#
# This source file is part of the EdgeDB open source project.
#
# Copyright 2019-present MagicStack Inc. and the EdgeDB authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

from __future__ import annotations
from typing import Optional, Iterable, Iterator

import json
import os.path
import re
import textwrap
import unittest
import uuid

import edgedb

from edb.common import assert_data_shape

from edb.testbase import server as tb
from edb.testbase import serutils
from edb.tools import test


class EdgeQLDataMigrationTestCase(tb.DDLTestCase):
    """Test that migrations preserve data under certain circumstances.

    Renaming, changing constraints, increasing cardinality should not
    destroy data.

    Some of the test cases here use the same migrations as
    `test_schema_migrations_equivalence`, therefore the test numbers
    should match for easy reference, even if it means skipping some.
    """

    DEFAULT_MODULE = 'test'

    def setUp(self):
        super().setUp()
        self._ignore = self.ignore_warnings(
            'Non-simple_scoping will be removed'
        )
        self._ignore.__enter__()

    def tearDown(self):
        super().tearDown()
        self._ignore.__exit__(None, None, None)

    def normalize_statement(self, s: str) -> str:
        re_filter = re.compile(r'[\s]+|(#.*?(\n|$))|(,(?=\s*[})]))')
        stripped = textwrap.dedent(s.lstrip('\n')).rstrip('\n')
        folded = re_filter.sub('', stripped).lower()
        return folded

    def cleanup_migration_exp_json(self, exp_result_json):
        # Cleanup the expected values by dedenting/stripping them
        if 'confirmed' in exp_result_json:
            exp_result_json['confirmed'] = [
                self.normalize_statement(v)
                for v in exp_result_json['confirmed']
            ]
        if (
            'proposed' in exp_result_json
            and exp_result_json['proposed']
            and 'statements' in exp_result_json['proposed']
        ):
            for stmt in exp_result_json['proposed']['statements']:
                stmt['text'] = self.normalize_statement(stmt['text'])

    async def assert_describe_migration(self, exp_result_json, *, msg=None):
        self.cleanup_migration_exp_json(exp_result_json)

        try:
            res = await self.con.query_single(
                'DESCRIBE CURRENT MIGRATION AS JSON;')

            res = json.loads(res)
            self.cleanup_migration_exp_json(res)
            assert_data_shape.assert_data_shape(
                res, exp_result_json, self.fail, message=msg)
        except Exception:
            self.add_fail_notes(serialization='json')
            raise

    async def fast_forward_describe_migration(
        self,
        *,
        limit: Optional[int] = None,
        user_input: Optional[Iterable[str]] = None,
        commit: bool = True,
    ):
        '''Repeatedly get the next step from DESCRIBE and execute it.

        The point of this as opposed to just using "POPULATE
        MIGRATION; COMMIT MIGRATION;" is that we want to make sure
        that the generated DDL is valid and in case it's not, narrow
        down which step is causing issues.
        '''

        # Keep track of proposed DDL
        prevddl = ''

        if user_input is None:
            input_iter: Iterator[str] = iter(tuple())
        else:
            input_iter = iter(user_input)

        try:
            step = 0
            while True:
                mig = await self.con.query_single(
                    'DESCRIBE CURRENT MIGRATION AS JSON;')
                mig = json.loads(mig)
                if mig['proposed'] is None:
                    assert_data_shape.assert_data_shape(
                        mig, {'complete': True},
                        self.fail,
                        message='No more "proposed", but not "completed" '
                                'either.'
                    )
                    if commit:
                        await self.con.execute('COMMIT MIGRATION;')
                    break

                interpolations = {}

                user_input_reqs = mig['proposed']['required_user_input']
                if user_input_reqs:
                    for var in user_input_reqs:
                        var_name = var['placeholder']
                        var_desc = var['prompt']
                        try:
                            var_value = next(input_iter)
                        except StopIteration:
                            raise AssertionError(
                                f'missing input value for prompt: {var_desc}'
                            ) from None

                        interpolations[var_name] = var_value

                for stmt in mig['proposed']['statements']:
                    curddl = stmt['text']

                    if interpolations:
                        def _replace(match, interpolations=interpolations):
                            var_name = match.group(1)
                            var_value = interpolations.get(var_name)
                            if var_value is None:
                                raise AssertionError(
                                    f'missing value for '
                                    f'placeholder {var_name!r}'
                                )
                            return var_value

                        curddl = re.sub(r'\\\((\w+)\)', _replace, curddl)

                    if prevddl == curddl:
                        raise Exception(
                            f"Repeated previous proposed DDL {curddl!r}"
                        )
                    try:
                        await self.con.execute(curddl)
                    except Exception as exc:
                        raise Exception(
                            f"Error while processing {curddl!r}"
                        ) from exc
                    prevddl = curddl
                step += 1
                if limit is not None and step == limit:
                    break
        except Exception:
            self.add_fail_notes(serialization='json')
            raise

    async def start_migration(
        self,
        migration,
        *,
        populate: bool = False,
        module: str | None = 'test',
        explicit_modules: bool = False,
    ):
        if explicit_modules or module is None:
            migration_text = migration
        else:
            migration_text = f'''
                module {module} {{
                    {migration}
                }}
            '''

        mig = f"""
            START MIGRATION TO {{
                {migration_text}
            }};
        """
        await self.con.execute(mig)
        if populate:
            await self.con.execute('POPULATE MIGRATION;')

    async def migrate(
        self,
        migration,
        *,
        populate: bool = False,
        module: str | None = 'test',
        explicit_modules: bool = False,
        user_input: Optional[Iterable[str]] = None,
    ):
        async with self.con.transaction():
            await self.start_migration(
                migration,
                populate=populate,
                module=module,
                explicit_modules=explicit_modules,
            )
            await self.fast_forward_describe_migration(user_input=user_input)
        await self.assert_last_migration()

    async def interact(self, parts, check_complete=True):
        for part in parts:
            if isinstance(part, str):
                prompt = part
                ans = "y"
                user_input = None
            else:
                prompt, ans, *user_input = part

            await self.assert_describe_migration({
                'proposed': {'prompt': prompt}
            })

            if ans == "y":
                await self.fast_forward_describe_migration(
                    limit=1, user_input=user_input)
            else:
                await self.con.execute('''
                    ALTER CURRENT MIGRATION REJECT PROPOSED;
                ''')

        if check_complete:
            await self.assert_describe_migration({
                'complete': True
            })

    async def assert_last_migration(self):
        last_name = await self.con.query_single(
            '''
            select assert_single(
                sys::Branch.last_migration
                    filter sys::Branch.name = sys::get_current_database());
            '''
        )
        last_mig = await self.con.query_single(
            '''
            select assert_single(
                schema::Migration { name } filter not exists .<parents);
            '''
        )
        if last_mig:
            self.assertEqual(last_mig.name, last_name)
        else:
            self.assertIsNone(last_name)


class TestEdgeQLDataMigration(EdgeQLDataMigrationTestCase):
    async def test_edgeql_migration_simple_01(self):
        # Base case, ensuring a single SDL migration from a clean
        # state works.
        await self.migrate("""
            type NamedObject {
                required property name -> str;
                multi link related -> NamedObject {
                    property lang -> str;
                };
            };
        """)
        await self.con.execute("""
            SET MODULE test;

            INSERT NamedObject {
                name := 'Test'
            };

            INSERT NamedObject {
                name := 'Test 2',
                related := (SELECT DETACHED NamedObject
                            FILTER .name = 'Test')
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT
                    NamedObject {
                        related: {
                            name,
                            @lang
                        }
                    }
                FILTER
                    .name = 'Test 2';
            """,
            [
                {
                    'related': [{'name': 'Test', '@lang': None}],
                }
            ]
        )

    async def test_edgeql_migration_link_inheritance(self):
        schema_f = os.path.join(os.path.dirname(__file__), 'schemas',
                                'links_1.esdl')

        with open(schema_f) as f:
            schema = f.read()

        await self.migrate(schema)

        await self.con.execute('''
            SET MODULE test;

            INSERT Target1 {
                name := 'Target1_linkinh_2'
            };

            INSERT ObjectType01 {
                target := (SELECT Target1
                           FILTER .name = 'Target1_linkinh_2'
                           LIMIT 1)
            };

            INSERT Target0 {
                name := 'Target0_linkinh_2'
            };

            INSERT ObjectType23 {
                target := (SELECT Target0
                           FILTER .name = 'Target0_linkinh_2'
                           LIMIT 1)
            };
        ''')

        await self.con.query('DECLARE SAVEPOINT t0;')

        with self.assertRaisesRegex(
                edgedb.InvalidLinkTargetError,
                r"invalid target for link 'target' of object type "
                r"'test::ObjectType01': "
                r"'test::Target0' \(expecting 'test::Target1'\)"):
            # Target0 is not allowed to be targeted by ObjectType01, since
            # ObjectType01 inherits from ObjectType1 which requires more
            # specific Target1.
            await self.con.execute('''
                INSERT ObjectType01 {
                    target := (
                        SELECT
                            Target0
                        FILTER
                            .name = 'Target0_linkinh_2'
                        LIMIT 1
                    )
                };
            ''')

        schema_f = os.path.join(os.path.dirname(__file__), 'schemas',
                                'links_1_migrated.esdl')

        with open(schema_f) as f:
            schema = f.read()

        await self.con.query('ROLLBACK TO SAVEPOINT t0')
        await self.migrate(schema)

    async def test_edgeql_migration_describe_reject_01(self):
        await self.migrate('''
            type Foo;
        ''')

        await self.start_migration('''
            type Bar;
        ''')

        await self.assert_describe_migration({
            'proposed': {
                'statements': [{
                    'text': """
                        ALTER TYPE test::Foo RENAME TO test::Bar;
                    """
                }]
            }
        })

        await self.con.execute('''
            ALTER CURRENT MIGRATION REJECT PROPOSED;
        ''')

        await self.assert_describe_migration({
            'proposed': {
                'statements': [{
                    'text': """
                        CREATE TYPE test::Bar;
                    """
                }]
            }
        })

        await self.con.execute('''
            ALTER CURRENT MIGRATION REJECT PROPOSED;
        ''')

        await self.assert_describe_migration({
            'proposed': {
                'statements': [{
                    'text': """
                        DROP TYPE test::Foo;
                    """
                }]
            }
        })

        await self.con.execute('''
            ALTER CURRENT MIGRATION REJECT PROPOSED;
        ''')

        await self.assert_describe_migration({
            'proposed': None,
            'complete': False,
        })

    async def test_edgeql_migration_describe_reject_02(self):
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                };
            };
        ''')

        await self.assert_describe_migration({
            'parent': 'm1a2l6lbzimqokzygdzbkyjrhbmjh3iljg7i2m6r2ias2z2de4x4cq',
            'confirmed': [],
            'complete': True,
            'proposed': None,
        })

        # Reject an empty proposal, which should be an idempotent
        # operation. So reject it several times.
        await self.con.execute('''
            ALTER CURRENT MIGRATION REJECT PROPOSED;
            ALTER CURRENT MIGRATION REJECT PROPOSED;
            ALTER CURRENT MIGRATION REJECT PROPOSED;
        ''')

    async def test_edgeql_migration_describe_reject_03(self):
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Type0;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': 'CREATE TYPE test::Type0;'
                }],
                'prompt': "did you create object type 'test::Type0'?",
            },
        })

        # Reject a proposal until we run out of options.
        await self.con.execute('''
            ALTER CURRENT MIGRATION REJECT PROPOSED;
            ALTER CURRENT MIGRATION REJECT PROPOSED;
            ALTER CURRENT MIGRATION REJECT PROPOSED;
            ALTER CURRENT MIGRATION REJECT PROPOSED;
            ALTER CURRENT MIGRATION REJECT PROPOSED;
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': None,
        })

    async def test_edgeql_migration_describe_reject_04(self):
        # Migration involving 2 modules
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Test;
                };

                module other {
                    type Test;
                };
            };
        ''')
        await self.fast_forward_describe_migration()

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Test2;
                };

                module other {
                    type Test3;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': 'ALTER TYPE other::Test RENAME TO other::Test3;',
                }],
                'prompt': (
                    "did you rename object type 'other::Test' to "
                    "'other::Test3'?"
                ),
            },
        })

        await self.con.execute('''
            ALTER CURRENT MIGRATION REJECT PROPOSED;
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': 'ALTER TYPE other::Test RENAME TO test::Test2;',
                }],
                'prompt': (
                    "did you rename object type 'other::Test' to "
                    "'test::Test2'?"
                ),
            },
        })

        await self.con.execute('''
            ALTER TYPE other::Test RENAME TO test::Test2;
        ''')

        await self.assert_describe_migration({
            'confirmed': [
                'ALTER TYPE other::Test RENAME TO test::Test2;'
            ],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': 'ALTER TYPE test::Test RENAME TO other::Test3;',
                }],
                'prompt': (
                    "did you rename object type 'test::Test' to "
                    "'other::Test3'?"
                ),
            },
        })

        await self.con.execute('''
            ALTER CURRENT MIGRATION REJECT PROPOSED;
        ''')

        await self.assert_describe_migration({
            'confirmed': [
                'ALTER TYPE other::Test RENAME TO test::Test2;'
            ],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': 'CREATE TYPE other::Test3;',
                }],
                'prompt': (
                    "did you create object type 'other::Test3'?"
                ),
            },
        })

        # Change our mind and use a rejected operation to rename the
        # type after all. So, we should be done now.
        await self.con.execute('''
            ALTER TYPE test::Test RENAME TO other::Test3;
        ''')

        await self.assert_describe_migration({
            'confirmed': [
                'ALTER TYPE other::Test RENAME TO test::Test2;',
                'ALTER TYPE test::Test RENAME TO other::Test3;',
            ],
            'complete': True,
            'proposed': None,
        })

    async def test_edgeql_migration_describe_reject_05(self):
        await self.migrate('''
            type User {
                required property username -> str {
                    constraint exclusive;
                    constraint regexp(r'asdf');
                }
            }
        ''')

        await self.start_migration('''
            type User {
                required property username -> str {
                    constraint exclusive;
                    constraint regexp(r'foo');
                }
            }
        ''')

        await self.assert_describe_migration({
            'proposed': {
                'prompt': (
                    "did you drop constraint 'std::regexp' "
                    "of property 'username'?"
                )
            }
        })

        await self.con.execute('''
            ALTER CURRENT MIGRATION REJECT PROPOSED;
        ''')

        # ... nothing to do, we can't get it
        await self.assert_describe_migration({
            'complete': False,
            'proposed': None,
        })

    async def test_edgeql_migration_describe_module_01(self):
        # Migration that creates a new module.
        await self.con.execute('''
            START MIGRATION TO {
                module new_module {
                    type Type0;
                };
            };
        ''')

        # Validate that we create a 'new_module'
        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': 'CREATE MODULE new_module IF NOT EXISTS;'
                }],
            },
        })

        # Auto-complete migration
        await self.fast_forward_describe_migration()

        # Drop the 'new_module'
        await self.con.execute('''
            START MIGRATION TO {
                module default {};
            };
        ''')

        # Validate that we drop a 'new_module'
        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': 'DROP TYPE new_module::Type0;'
                }],
            },
        })
        await self.con.execute('''
            DROP TYPE new_module::Type0;
        ''')

        await self.assert_describe_migration({
            'confirmed': [
                'DROP TYPE new_module::Type0;'
            ],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': 'DROP MODULE new_module;'
                }],
            },
        })

        # Auto-complete migration
        await self.fast_forward_describe_migration()

        # Make sure that 'new_module' can be created again with no
        # problems (i.e. it was dropped cleanly).
        await self.con.execute('''
            START MIGRATION TO {
                module new_module {
                    type Type0;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': 'CREATE MODULE new_module IF NOT EXISTS;'
                }],
            },
        })
        await self.con.execute('''
            CREATE MODULE new_module;
        ''')

        await self.assert_describe_migration({
            'confirmed': [
                'CREATE MODULE new_module;',
            ],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': 'CREATE TYPE new_module::Type0;'
                }],
            },
        })
        await self.con.execute('''
            CREATE TYPE new_module::Type0;
            COMMIT MIGRATION;
        ''')

        await self.assert_query_result(
            r"""
                INSERT new_module::Type0;
            """,
            [{
                'id': uuid.UUID,
            }],
        )

    async def test_edgeql_migration_describe_type_01(self):
        # Migration that renames a type.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Type1;
                };
            };
        ''')

        await self.assert_describe_migration({
            'parent': 'm1a2l6lbzimqokzygdzbkyjrhbmjh3iljg7i2m6r2ias2z2de4x4cq',
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'CREATE TYPE test::Type1;'
                    )
                }],
                'prompt': "did you create object type 'test::Type1'?",
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        res = await self.con.query(r'''INSERT test::Type1;''')

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Type01;
                };
            };
        ''')

        await self.assert_describe_migration({
            'parent': 'm1jywblj6c7z25ouifcicpxniu37jdpyunf62q4th7isdafcqu67gq',
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'ALTER TYPE test::Type1 RENAME TO test::Type01;'
                    )
                }],
                'prompt': (
                    "did you rename object type 'test::Type1' to "
                    "'test::Type01'?"
                ),
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result('''
            SELECT test::Type01;
        ''', [{'id': res[0].id}])

    async def test_edgeql_migration_describe_type_02(self):
        # Migration that creates a type.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Type02;
                };
            };
        ''')

        await self.assert_describe_migration({
            'parent': 'm1a2l6lbzimqokzygdzbkyjrhbmjh3iljg7i2m6r2ias2z2de4x4cq',
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'CREATE TYPE test::Type02;'
                    )
                }],
                'prompt': "did you create object type 'test::Type02'?",
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.con.query(r'''INSERT test::Type02;''')

        # Migration that drops a type.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                };
            };
        ''')

        await self.assert_describe_migration({
            'parent': 'm1fcvk56n44i62qwjnw5nqgafnbpulfhhaeb6kxqhh4c6lc4elwysa',
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'DROP TYPE test::Type02;'
                    )
                }],
                'prompt': (
                    "did you drop object type 'test::Type02'?"
                ),
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result('''
            WITH MODULE schema
            SELECT ObjectType
            FILTER .name = 'test::Type02';
        ''', [])

        # Make sure that type dropped cleanly by re-creating and
        # using the type again.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Type02;
                };
            };
        ''')

        await self.assert_describe_migration({
            'parent': 'm1yee6qj63nps27cjnrcudwiupusdqkzrwistpvfbqf2fstcmwauwa',
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'CREATE TYPE test::Type02;'
                    )
                }],
                'prompt': "did you create object type 'test::Type02'?",
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result('''
            INSERT test::Type02;
        ''', [{'id': uuid.UUID}])

    async def test_edgeql_migration_describe_type_03(self):
        await self.migrate('''
            type Type0;
        ''')

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Type1;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': "ALTER TYPE test::Type0 RENAME TO test::Type1;"
                }],
                'prompt': (
                    "did you rename object type 'test::Type0' to "
                    "'test::Type1'?"
                ),
            },
        })

        # Instead of the suggestion do a couple of different, but
        # equivalent commands.
        await self.con.execute('''
            ALTER TYPE test::Type0 RENAME TO test::TypeXX;
            ALTER TYPE test::TypeXX RENAME TO test::Type1;
        ''')

        await self.assert_describe_migration({
            'confirmed': [
                'ALTER TYPE test::Type0 RENAME TO test::TypeXX;',
                'ALTER TYPE test::TypeXX RENAME TO test::Type1;',
            ],
            'complete': True,
            'proposed': None,
        })

    async def test_edgeql_migration_describe_type_04(self):
        self.maxDiff = None
        await self.migrate('''
            type Test;
        ''')

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Test2;
                    type Test3;
                };
            };
        ''')

        await self.assert_describe_migration({
            'parent': 'm1xh653zionj2aehqbh7x6km5lo3b2mjaftxdkvqoh3wluc3iv6k2a',
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': 'ALTER TYPE test::Test RENAME TO test::Test2;',
                }],
                'prompt': (
                    "did you rename object type 'test::Test' to 'test::Test2'?"
                ),
            },
        })

        await self.con.execute('''
            ALTER CURRENT MIGRATION REJECT PROPOSED;
        ''')

        await self.assert_describe_migration({
            'parent': 'm1xh653zionj2aehqbh7x6km5lo3b2mjaftxdkvqoh3wluc3iv6k2a',
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': 'ALTER TYPE test::Test RENAME TO test::Test3;',
                }],
                'prompt': (
                    "did you rename object type 'test::Test' to 'test::Test3'?"
                ),
            },
        })

        await self.con.execute('''
            ALTER TYPE test::Test RENAME TO test::Test3;
        ''')

        await self.assert_describe_migration({
            'parent': 'm1xh653zionj2aehqbh7x6km5lo3b2mjaftxdkvqoh3wluc3iv6k2a',
            'confirmed': ['ALTER TYPE test::Test RENAME TO test::Test3;'],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': 'CREATE TYPE test::Test2;',
                }],
                'prompt': (
                    "did you create object type 'test::Test2'?"
                ),
            },
        })

    async def test_edgeql_migration_describe_property_01(self):
        # Migration that renames a property.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Type01 {
                        property field1 -> str;
                    };
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'CREATE TYPE test::Type01 {\n'
                        '    CREATE PROPERTY field1'
                        ': std::str;\n'
                        '};'
                    )
                }],
                'prompt': "did you create object type 'test::Type01'?",
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.con.execute('''
            INSERT test::Type01 {
                field1 := 'prop_test'
            };
        ''')

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Type01 {
                        property field01 -> str;
                    };
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'ALTER TYPE test::Type01 {\n'
                        '    ALTER PROPERTY field1 {\n'
                        '        RENAME TO field01;\n'
                        '    };\n'
                        '};'
                    )
                }],
                'prompt': (
                    "did you rename property 'field1' of object type"
                    " 'test::Type01' to 'field01'?"
                ),
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result('''
            SELECT test::Type01 {
                field01
            };
        ''', [{'field01': 'prop_test'}])

    async def test_edgeql_migration_describe_property_02(self):
        # Migration that creates a type with property.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Type02 {
                        property field02 -> str;
                    };
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'CREATE TYPE test::Type02 {\n'
                        '    CREATE PROPERTY field02'
                        ': std::str;\n'
                        '};'
                    )
                }],
                'prompt': "did you create object type 'test::Type02'?",
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        res = await self.con.query('''
            INSERT test::Type02 {
                field02 := 'prop_test'
            };
        ''')

        # Migration that drops a property.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Type02;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'ALTER TYPE test::Type02 {\n'
                        '    DROP PROPERTY field02;\n'
                        '};'
                    )
                }],
                'prompt': (
                    "did you drop property 'field02'"
                    " of object type 'test::Type02'?"
                ),
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result('''
            SELECT test::Type02 {
                id
            };
        ''', [{
            'id': res[0].id
        }])

        # Make sure that property dropped cleanly by re-creating and
        # using the property again.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Type02 {
                        property field02 -> str;
                    };
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'ALTER TYPE test::Type02 {\n'
                        '    CREATE PROPERTY field02'
                        ': std::str;\n'
                        '};'
                    )
                }],
                'prompt': (
                    "did you create property 'field02'"
                    " of object type 'test::Type02'?"
                ),
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result('''
            SELECT test::Type02 {
                id,
                field02,
            };
        ''', [{
            'id': res[0].id,
            'field02': None,
        }])

    async def test_edgeql_migration_describe_link_01(self):
        # Migration that renames a link.
        await self.con.execute(r'''
            START MIGRATION TO {
                module test {
                    type Foo;
                    type Type01 {
                        link foo1 -> Foo;
                    };
                };
            };

            # just initialize Foo, since we're interested in the other type
            CREATE TYPE test::Foo;
        ''')

        await self.assert_describe_migration({
            'confirmed': ['CREATE TYPE test::Foo;'],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'CREATE TYPE test::Type01 {\n'
                        '    CREATE LINK foo1'
                        ': test::Foo;\n'
                        '};'
                    )
                }],
                'prompt': "did you create object type 'test::Type01'?",
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        res = await self.con.query('''
            WITH MODULE test
            SELECT (
                INSERT Type01 {
                    foo1 := (INSERT Foo)
                }
            ) {
                foo1
            };
        ''')

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Foo;
                    type Type01 {
                        link foo01 -> Foo;
                    };
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'ALTER TYPE test::Type01 {\n'
                        '    ALTER LINK foo1 {\n'
                        '        RENAME TO foo01;\n'
                        '    };\n'
                        '};'
                    )
                }],
                'prompt': (
                    "did you rename link 'foo1' of object type"
                    " 'test::Type01' to 'foo01'?"
                ),
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result('''
            SELECT test::Type01 {
                foo01: {
                    id
                }
            };
        ''', [{'foo01': {'id': res[0].foo1.id}}])

    async def test_edgeql_migration_describe_link_02(self):
        # Migration that creates a type with link.
        await self.con.execute(r'''
            START MIGRATION TO {
                module test {
                    type Foo;
                    type Type02 {
                        link foo02 -> Foo;
                    };
                };
            };

            # just initialize Foo, since we're interested in the other type
            CREATE TYPE test::Foo;
        ''')

        await self.assert_describe_migration({
            'confirmed': ['CREATE TYPE test::Foo;'],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'CREATE TYPE test::Type02 {\n'
                        '    CREATE LINK foo02'
                        ': test::Foo;\n'
                        '};'
                    )
                }],
                'prompt': "did you create object type 'test::Type02'?",
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        res = await self.con.query('''
            WITH MODULE test
            SELECT (
                INSERT Type02 {
                    foo02 := (INSERT Foo)
                }
            ) {
                foo02
            }
        ''')

        # Migration that drops a link.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Foo;
                    type Type02;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'ALTER TYPE test::Type02 {\n'
                        '    DROP LINK foo02;\n'
                        '};'
                    )
                }],
                'prompt': (
                    "did you drop link 'foo02' of object type 'test::Type02'?"
                ),
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result('''
            SELECT test::Type02 {
                id
            };
        ''', [{
            'id': res[0].id
        }])
        await self.assert_query_result('''
            SELECT test::Foo {
                id
            };
        ''', [{
            'id': res[0].foo02.id
        }])

        # Make sure that link dropped cleanly by re-creating and
        # using the link again.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Foo;
                    type Type02 {
                        link foo02 -> Foo;
                    };
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'ALTER TYPE test::Type02 {\n'
                        '    CREATE LINK foo02'
                        ': test::Foo;\n'
                        '};'
                    )
                }],
                'prompt': (
                    "did you create link 'foo02'"
                    " of object type 'test::Type02'?"
                ),
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result('''
            SELECT test::Type02 {
                id,
                foo02: {
                    id
                },
            };
        ''', [{
            'id': res[0].id,
            'foo02': None,
        }])

    async def test_edgeql_migration_describe_link_03(self):
        # Migration that renames a link.
        await self.con.execute(r'''
            START MIGRATION TO {
                module test {
                    abstract link foo3;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'CREATE ABSTRACT LINK test::foo3;'
                    )
                }],
                'prompt': "did you create abstract link 'test::foo3'?",
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    abstract link foo03;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'ALTER ABSTRACT LINK test::foo3 '
                        'RENAME TO test::foo03;'
                    )
                }],
                'prompt': (
                    "did you rename abstract link 'test::foo3' to "
                    "'test::foo03'?"
                ),
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'DROP ABSTRACT LINK test::foo03;'
                    )
                }],
                'prompt': (
                    "did you drop abstract link 'test::foo03'?"
                ),
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

    async def test_edgeql_migration_describe_index_01(self):
        # Migration that creates index.
        await self.con.execute(r'''
            START MIGRATION TO {
                module test {
                    type Foo {
                        property a -> int64;
                    };
                };
            };
        ''')
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Foo {
                        property a -> int64;
                        index on (.a)
                    };
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'prompt': (
                    "did you create index on (.a) "
                    "of object type 'test::Foo'?"
                )
            }
        })

    async def test_edgeql_migration_describe_index_02(self):
        # Migration that drops index expression.
        await self.con.execute(r'''
            START MIGRATION TO {
                module test {
                    type Foo {
                        property a -> int64;
                        index on (.a)
                    };
                };
            };
        ''')
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Foo {
                        property a -> int64;
                    };
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'prompt': (
                    "did you drop index on (.a) "
                    "of object type 'test::Foo'?"
                )
            }
        })

    async def test_edgeql_migration_describe_index_03(self):
        # Migration that creates index on link property
        await self.con.execute(r'''
            START MIGRATION TO {
                module test {
                    type Foo {
                        link bar -> Bar {
                            baz -> int64;
                        }
                    };
                    type Bar;
                };
            };
        ''')
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Foo {
                        link bar -> Bar {
                            baz -> int64;
                            index on (@baz);
                        }
                    };
                    type Bar;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'prompt': (
                    "did you create index on (@baz) "
                    "of link 'bar'?"
                )
            }
        })

    async def test_edgeql_migration_describe_index_04(self):
        # Migration that drops index on link property
        await self.con.execute(r'''
            START MIGRATION TO {
                module test {
                    type Foo {
                        link bar -> Bar {
                            baz -> int64;
                            index on (@baz);
                        }
                    };
                    type Bar;
                };
            };
        ''')
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Foo {
                        link bar -> Bar {
                            baz -> int64;
                        }
                    };
                    type Bar;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'prompt': (
                    "did you drop index on (@baz) "
                    "of link 'bar'?"
                )
            }
        })

    async def test_edgeql_migration_describe_index_05(self):
        # Migration that creates index.
        await self.con.execute('''
            START MIGRATION TO {
                module default {
                    type Foo {
                        property x -> int64;
                        index on (.x);
                    };
                };
            };
        ''')
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.con.execute('''
            START MIGRATION TO {
                module default {
                    type Foo {
                        property y -> int64;
                        index on (.y);
                    };
                };
            };
        ''')

        await self.interact([
            ("did you drop index on (.x) of object type 'default::Foo'?", "n"),
            "did you rename property 'x' of object type 'default::Foo' to 'y'?",
        ])

    async def test_edgeql_migration_describe_scalar_01(self):
        # Migration that renames a type.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    scalar type ScalarType1 extending int64;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'CREATE SCALAR TYPE test::ScalarType1'
                        ' EXTENDING std::int64;'
                    )
                }],
                'prompt': "did you create scalar type 'test::ScalarType1'?",
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result('''
            SELECT <test::ScalarType1>'1' + 2;
        ''', [3])

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    scalar type ScalarType01 extending int64;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'ALTER SCALAR TYPE test::ScalarType1'
                        ' RENAME TO test::ScalarType01;'
                    )
                }],
                'prompt': (
                    "did you rename scalar type 'test::ScalarType1' to "
                    "'test::ScalarType01'?"
                ),
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result('''
            SELECT <test::ScalarType01>'2' + 1;
        ''', [3])

    async def test_edgeql_migration_describe_scalar_02(self):
        # Migration that creates a type.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    scalar type ScalarType02 extending str;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'CREATE SCALAR TYPE test::ScalarType02'
                        ' EXTENDING std::str;'
                    )
                }],
                'prompt': "did you create scalar type 'test::ScalarType02'?",
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result('''
            SELECT <test::ScalarType02>1 ++ '2';
        ''', ['12'])

        # Migration that drops a type.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'DROP SCALAR TYPE test::ScalarType02;'
                    )
                }],
                'prompt': (
                    "did you drop scalar type 'test::ScalarType02'?"
                ),
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result('''
            WITH MODULE schema
            SELECT ScalarType
            FILTER .name = 'test::ScalarType02';
        ''', [])

        # Make sure that type dropped cleanly by re-creating and
        # using the type again.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    scalar type ScalarType02 extending str;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'CREATE SCALAR TYPE test::ScalarType02'
                        ' EXTENDING std::str;'
                    )
                }],
                'prompt': "did you create scalar type 'test::ScalarType02'?",
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result('''
            SELECT <test::ScalarType02>2 ++ '1';
        ''', ['21'])

    async def test_edgeql_migration_describe_enum_01(self):
        # Migration that renames an enum.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    scalar type EnumType1 extending enum<foo, bar>;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        "CREATE SCALAR TYPE test::EnumType1"
                        " EXTENDING enum<foo, bar>;"
                    )
                }],
                'prompt': "did you create scalar type 'test::EnumType1'?",
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result('''
            SELECT <test::EnumType1>'bar';
        ''', ['bar'])

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    scalar type EnumType01 extending enum<foo, bar>;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'ALTER SCALAR TYPE test::EnumType1'
                        ' RENAME TO test::EnumType01;'
                    )
                }],
                'prompt': (
                    "did you rename scalar type 'test::EnumType1' to "
                    "'test::EnumType01'?"
                ),
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result('''
            SELECT <test::EnumType01>'foo';
        ''', ['foo'])

    async def test_edgeql_migration_describe_enum_02(self):
        # Migration that creates an enum.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    scalar type EnumType02 extending enum<foo, bar>;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        "CREATE SCALAR TYPE test::EnumType02"
                        " EXTENDING enum<foo, bar>;"
                    )
                }],
                'prompt': "did you create scalar type 'test::EnumType02'?",
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result('''
            SELECT <test::EnumType02>'bar';
        ''', ['bar'])

        # Migration that drops an enum.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'DROP SCALAR TYPE test::EnumType02;'
                    )
                }],
                'prompt': (
                    "did you drop scalar type 'test::EnumType02'?"
                ),
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result('''
            WITH MODULE schema
            SELECT ScalarType
            FILTER .name = 'test::EnumType02';
        ''', [])

        # Make sure that enum dropped cleanly by re-creating and
        # using the enum again.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    scalar type EnumType02 extending enum<foo, bar>;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        "CREATE SCALAR TYPE test::EnumType02"
                        " EXTENDING enum<foo, bar>;"
                    )
                }],
                'prompt': "did you create scalar type 'test::EnumType02'?",
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result('''
            SELECT <test::EnumType02>'foo';
        ''', ['foo'])

    async def test_edgeql_migration_describe_annotation_01(self):
        # Migration that renames an annotation.
        await self.migrate('''
            abstract annotation my_anno1;
        ''')

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    abstract annotation renamed_anno1;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'ALTER ABSTRACT ANNOTATION test::my_anno1 '
                        'RENAME TO test::renamed_anno1;'
                    )
                }],
            },
        })

    async def test_edgeql_migration_describe_annotation_02(self):
        # Migration that creates an annotation.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    abstract annotation my_anno2;

                    type AnnoType2 {
                        annotation my_anno2 := 'test_my_anno2';
                    }
                };
            };
        ''')

        await self.con.execute('''
            CREATE TYPE test::AnnoType2;
        ''')

        await self.assert_describe_migration({
            'confirmed': [
                'CREATE TYPE test::AnnoType2;'
            ],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'CREATE ABSTRACT ANNOTATION test::my_anno2;'
                    )
                }],
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        # Migration that drops an annotation.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type AnnoType2;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'ALTER TYPE test::AnnoType2 {\n'
                        '    DROP ANNOTATION test::my_anno2;\n'
                        '};'
                    )
                }],
            },
        })
        # Auto-complete migration
        await self.con.execute('''
            ALTER TYPE test::AnnoType2 {
                DROP ANNOTATION test::my_anno2;
            };
        ''')

        await self.assert_describe_migration({
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'DROP ABSTRACT ANNOTATION test::my_anno2;'
                    )
                }],
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        # Make sure that annotation dropped cleanly by re-creating and
        # using the annotation.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    abstract annotation my_anno2;

                    type AnnoType2 {
                        annotation my_anno2 := 'retest_my_anno2';
                    }
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'CREATE ABSTRACT ANNOTATION test::my_anno2;'
                    )
                }],
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    annotations: {
                        name,
                        @value,
                    },
                } FILTER .name = 'test::AnnoType2';
            """,
            [{
                'name': 'test::AnnoType2',
                'annotations': [{
                    'name': 'test::my_anno2',
                    '@value': 'retest_my_anno2',
                }]
            }],
        )

    async def test_edgeql_migration_describe_constraint_01(self):
        # Migration that renames a constraint.
        await self.migrate('''
            abstract constraint my_oneof(one_of: array<anytype>) {
                using (contains(one_of, __subject__));
            };

            type Foo {
                property note -> str {
                    constraint my_oneof(["foo", "bar"]);
                }
            }
        ''')

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    abstract constraint my_one_of(one_of: array<anytype>) {
                        using (contains(one_of, __subject__));
                    };

                    type Foo {
                        property note -> str {
                            constraint my_one_of(["foo", "bar"]);
                        }
                    }
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'ALTER ABSTRACT CONSTRAINT test::my_oneof '
                        'RENAME TO test::my_one_of;'
                    )
                }],
            },
        })

        await self.fast_forward_describe_migration()

    async def test_edgeql_migration_describe_constraint_02(self):
        # Migration that renames a link constraint.
        # Honestly I'm not sure if link constraints can really be
        # anything other than exclusive?
        await self.migrate('''
            abstract constraint my_exclusive() extending std::exclusive;

            type Foo;
            type Bar {
                link foo -> Foo {
                    constraint my_exclusive;
                }
            }
        ''')

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    abstract constraint myexclusive() extending std::exclusive;

                    type Foo;
                    type Bar {
                        link foo -> Foo {
                            constraint myexclusive;
                        }
                    }
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'ALTER ABSTRACT CONSTRAINT test::my_exclusive '
                        'RENAME TO test::myexclusive;'
                    )
                }],
            },
        })

        await self.fast_forward_describe_migration()

    async def test_edgeql_migration_describe_constraint_03(self):
        # Migration that renames a object constraint.
        await self.migrate('''
            abstract constraint my_oneof(one_of: array<anytype>) {
                using (contains(one_of, __subject__));
            };

            type Foo {
                property a -> str;
                property b -> str;
                constraint my_oneof(["foo", "bar"])
                    ON (__subject__.a++__subject__.b);
            }
        ''')

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    abstract constraint my_one_of(one_of: array<anytype>) {
                        using (contains(one_of, __subject__));
                    };

                    type Foo {
                        property a -> str;
                        property b -> str;
                        constraint my_one_of(["foo", "bar"])
                            ON (__subject__.a++__subject__.b);
                    }
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'ALTER ABSTRACT CONSTRAINT test::my_oneof '
                        'RENAME TO test::my_one_of;'
                    )
                }],
            },
        })

        await self.fast_forward_describe_migration()

    async def test_edgeql_migration_describe_constraint_04(self):
        # Migration that creates a constraint.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    abstract constraint my_one_of(one_of: array<anytype>) {
                        using (contains(one_of, __subject__));
                    };

                    scalar type my_str extending str {
                        constraint my_one_of(['my', 'str']);
                    };
                };
            };
        ''')

        await self.con.execute('''
            CREATE SCALAR TYPE test::my_str EXTENDING std::str;
        ''')

        await self.assert_describe_migration({
            'confirmed': [
                'CREATE SCALAR TYPE test::my_str EXTENDING std::str;'
            ],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'CREATE ABSTRACT CONSTRAINT test::my_one_of('
                        'one_of: array<anytype>) {\n'
                        '    USING (std::contains(one_of, __subject__));\n'
                        '};'
                    ),
                }],
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()
        await self.con.query('DECLARE SAVEPOINT migration_01')

        await self.assert_query_result(
            r"""
                SELECT <test::my_str>'my';
            """,
            ['my'],
        )
        with self.assertRaisesRegex(
                edgedb.ConstraintViolationError,
                r"invalid my_str"):
            await self.con.execute(r"""
                SELECT <test::my_str>'nope';
            """)
        await self.con.query('ROLLBACK TO SAVEPOINT migration_01')

        # Migration that drops a constraint.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    scalar type my_str extending str;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        "ALTER SCALAR TYPE test::my_str {\n"
                        "    DROP CONSTRAINT test::my_one_of(['my', 'str']);\n"
                        "};"
                    ),
                }],
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result(
            r"""
                SELECT <test::my_str>'my';
            """,
            ['my'],
        )
        await self.assert_query_result(
            r"""
                SELECT <test::my_str>'nope';
            """,
            ['nope'],
        )

        # Test that dropping constraint was clean with a migration
        # that re-creates a constraint.
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    abstract constraint my_one_of(one_of: array<anytype>) {
                        using (contains(one_of, __subject__));
                    };

                    scalar type my_str extending str {
                        constraint my_one_of(['my2', 'str2']);
                    };
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'CREATE ABSTRACT CONSTRAINT '
                        'test::my_one_of(one_of: array<anytype>) {\n'
                        '    USING (std::contains(one_of, __subject__));\n'
                        '};'
                    ),
                }],
            },
        })
        # Auto-complete migration
        await self.fast_forward_describe_migration()

        await self.assert_query_result(
            r"""
                SELECT <test::my_str>'my2';
            """,
            ['my2'],
        )
        with self.assertRaisesRegex(
                edgedb.ConstraintViolationError,
                r"invalid my_str"):
            await self.con.execute(r"""
                SELECT <test::my_str>'my';
            """)

    async def test_edgeql_migration_describe_abs_ptr_01(self):
        await self.migrate('''
            abstract link abs_link;
        ''')

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    abstract link new_abs_link;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'ALTER ABSTRACT LINK test::abs_link '
                        'RENAME TO test::new_abs_link;'
                    )
                }],
            },
        })

    async def test_edgeql_migration_abs_ptr_01(self):
        await self.migrate(r"""
            type T { multi link following := T; }
        """)
        await self.migrate(r"""
            abstract link abs { property foo: str };
            type T { multi link following extending abs -> T; }
        """)

    async def test_edgeql_migration_describe_function_01(self):
        await self.migrate('''
            function foo(x: str) -> str using (SELECT <str>random());
        ''')

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    function bar(x: str) -> str using (SELECT <str>random());
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'ALTER FUNCTION test::foo(x: std::str) '
                        '{RENAME TO test::bar;};'
                    )
                }],
            },
        })

    async def test_edgeql_migration_function_01(self):
        await self.migrate('''
            type Note {
                required property name -> str;
            }

            function hello_note(x: Note) -> str {
                USING (SELECT x.name)
            }
        ''')

    async def test_edgeql_migration_function_02(self):
        await self.migrate('''
            type Foo;

            function foo(x: Foo) -> int64 {
                USING (SELECT 0)
            }
        ''')

        await self.migrate('''
            type Bar;

            function foo(x: Bar) -> int64 {
                USING (SELECT 0)
            }
        ''')

        await self.con.execute('''
            DROP FUNCTION test::foo(x: test::Bar);
        ''')

    async def test_edgeql_migration_function_03(self):
        await self.migrate('''
            type Foo;

            function foo(x: Foo) -> int64 {
                USING (SELECT 0)
            }
        ''')

        await self.migrate('''
            type Bar;

            function foo2(x: Bar) -> int64 {
                USING (SELECT 0)
            }
        ''')

        await self.con.execute('''
            DROP FUNCTION test::foo2(x: test::Bar);
        ''')

    async def test_edgeql_migration_function_04(self):
        await self.migrate('''
            function foo() -> str USING ('foo');
        ''')

        await self.start_migration('''
            function foo() -> str USING ('bar');
        ''')

        await self.interact([
            "did you alter function 'test::foo'?"
        ])
        await self.fast_forward_describe_migration()

        await self.assert_query_result(
            r"""
                SELECT test::foo()
            """,
            ["bar"],
        )

    async def test_edgeql_migration_function_05(self):
        await self.migrate("""
            type Person {
                required property name -> str {
                    constraint exclusive;
                }
                multi link places_visited -> Place;
            }

            type Place {
                required property name -> str {
                    constraint exclusive;
                }
            }

            function visited(person: str, city: str) -> bool
                using (
                    WITH person := (SELECT Person FILTER .name = person),
                    SELECT city IN person.places_visited.name
                );
        """)

    async def test_edgeql_migration_function_06(self):
        await self.migrate("""
            type Foo;
            type Bar;
            type Baz {
                optional link baz -> (Foo | Bar)
            };
            function getBaz(bar: Baz) -> optional (Foo | Bar)
                using(bar.baz)
        """)
        await self.con.execute('insert test::Baz {}')
        await self.assert_query_result(
            "select test::getBaz((select test::Baz limit 1))",
            [])
        await self.con.execute("drop function test::getBaz(baz: test::Baz)")

    async def test_edgeql_migration_constraint_01(self):
        await self.migrate('''
            abstract constraint not_bad {
                using (__subject__ != "bad" and __subject__ != "terrible")
            }

            type Foo {
                property foo -> str {
                    constraint not_bad;
                }
            }
            type Bar extending Foo;
        ''')

        await self.start_migration('''
            abstract constraint not_bad {
                using (__subject__ != "bad" and __subject__ != "awful")
            }

            type Foo {
                property foo -> str {
                    constraint not_bad;
                }
            }
            type Bar extending Foo;
        ''')

        await self.interact([
            "did you alter abstract constraint 'test::not_bad'?"
        ])
        await self.fast_forward_describe_migration()

        async with self.assertRaisesRegexTx(
            edgedb.ConstraintViolationError,
            "invalid foo",
        ):
            await self.con.execute(r"""
                INSERT test::Foo { foo := "awful" };
            """)

    async def test_edgeql_migration_describe_type_rename_01(self):
        await self.migrate('''
            type Foo;
            type Baz {
                link l -> Foo;
            };
        ''')

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Bar;
                    type Baz {
                        link l -> Bar;
                    };
                }
            };
            POPULATE MIGRATION;
        ''')

        await self.assert_describe_migration({
            'complete': True,
            'confirmed': [
                'ALTER TYPE test::Foo RENAME TO test::Bar;'
            ],
        })

        await self.fast_forward_describe_migration()

    async def test_edgeql_migration_describe_populate_describe(self):
        await self.start_migration('''
            type Foo;
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'CREATE TYPE test::Foo;'
                    )
                }],
            },
        })

        await self.con.execute('POPULATE MIGRATION;')

        await self.assert_describe_migration({
            'confirmed': ['CREATE TYPE test::Foo;'],
            'complete': True,
            'proposed': None,
        })

    async def test_edgeql_migration_computed_01(self):
        await self.migrate(r'''
            type Foo {
                property val -> str;
                property comp := count((
                    # Use an alias in WITH block in a computable
                    WITH x := .val
                    # Use an alias in SELECT in a computable
                    SELECT y := Bar FILTER x = y.val
                ))
            }

            type Bar {
                property val -> str;
            }
        ''')

        await self.con.execute("""
            SET MODULE test;

            INSERT Foo {val := 'c'};
            INSERT Foo {val := 'd'};

            INSERT Bar {val := 'a'};
            INSERT Bar {val := 'b'};
            INSERT Bar {val := 'c'};
            INSERT Bar {val := 'c'};
        """)

        await self.assert_query_result(
            r"""
                SELECT Foo {
                    val,
                    comp,
                } ORDER BY .val;
            """,
            [{
                'val': 'c',
                'comp': 2,
            }, {
                'val': 'd',
                'comp': 0,
            }],
        )

    async def test_edgeql_migration_computed_02(self):
        await self.migrate(r'''
            type Foo { property foo := '1' };
            type Bar extending Foo;
        ''')

        await self.migrate(r'''
            type Foo { property foo := 1 };
            type Bar extending Foo;
        ''')

    async def test_edgeql_migration_computed_03(self):
        await self.migrate(r'''
            type User {
                property name -> str;
                multi link tweets := Tweet;
            }
            type Tweet {
                property text -> str;
                link author -> User;
            }
        ''', module='default')

        await self.con.execute("""
            INSERT Tweet {
                text := 'Hello',
                author := (
                    INSERT User {name := 'Alice'}
                )
            };
            INSERT Tweet {
                text := 'Hi',
                author := (
                    INSERT User {name := 'Billie'}
                )
            };
        """)

        # Validate our structures
        await self.assert_query_result(
            r"""
                SELECT Tweet {
                    text,
                    author: {
                        name
                    },
                } ORDER BY .text;
            """,
            [{
                'text': 'Hello',
                'author': {
                    'name': 'Alice'
                },
            }, {
                'text': 'Hi',
                'author': {
                    'name': 'Billie'
                },
            }],
        )

        await self.assert_query_result(
            r"""
                SELECT User {
                    name,
                    tweets: {
                        text
                    } ORDER BY .text,
                } ORDER BY .name;
            """,
            [{
                'name': 'Alice',
                'tweets': [{
                    'text': 'Hello'
                }, {
                    'text': 'Hi'
                }],
            }, {
                'name': 'Billie',
                'tweets': [{
                    'text': 'Hello'
                }, {
                    'text': 'Hi'
                }],
            }],
        )

        await self.migrate(r'''
            type User {
                property name -> str;
                multi link tweets := User.<author[IS Tweet];
            }
            type Tweet {
                property text -> str;
                link author -> User;
            }
        ''', module='default')

        await self.assert_query_result(
            r"""
                SELECT User {
                    name,
                    tweets: {
                        text
                    } ORDER BY .text,
                } ORDER BY .name;
            """,
            [{
                'name': 'Alice',
                'tweets': [{
                    'text': 'Hello'
                }],
            }, {
                'name': 'Billie',
                'tweets': [{
                    'text': 'Hi'
                }],
            }],
        )

        await self.migrate(r'''
            type User {
                property name -> str;
                multi link tweets := .<author[IS Tweet];
            }
            type Tweet {
                property text -> str;
                link author -> User;
            }
        ''', module='default')

        await self.assert_query_result(
            r"""
                SELECT User {
                    name,
                    tweets: {
                        text
                    } ORDER BY .text,
                } ORDER BY .name;
            """,
            [{
                'name': 'Alice',
                'tweets': [{
                    'text': 'Hello'
                }],
            }, {
                'name': 'Billie',
                'tweets': [{
                    'text': 'Hi'
                }],
            }],
        )

        await self.migrate(r'''
            type User {
                property name -> str;
                multi link tweets := (
                    SELECT Tweet FILTER .author = User
                );
            }
            type Tweet {
                property text -> str;
                link author -> User;
            }
        ''', module='default')

        await self.assert_query_result(
            r"""
                SELECT User {
                    name,
                    tweets: {
                        text
                    } ORDER BY .text,
                } ORDER BY .name;
            """,
            [{
                'name': 'Alice',
                'tweets': [{
                    'text': 'Hello'
                }],
            }, {
                'name': 'Billie',
                'tweets': [{
                    'text': 'Hi'
                }],
            }],
        )

        await self.migrate(r'''
            type User {
                property name -> str;
                multi link tweets := (
                    WITH U := User
                    SELECT Tweet FILTER .author IN U
                );
            }
            type Tweet {
                property text -> str;
                link author -> User;
            }
        ''', module='default')

        await self.assert_query_result(
            r"""
                SELECT User {
                    name,
                    tweets: {
                        text
                    } ORDER BY .text,
                } ORDER BY .name;
            """,
            [{
                'name': 'Alice',
                'tweets': [{
                    'text': 'Hello'
                }],
            }, {
                'name': 'Billie',
                'tweets': [{
                    'text': 'Hi'
                }],
            }],
        )

        await self.migrate(r'''
            type User {
                property name -> str;
                multi link tweets := (
                    WITH U := DETACHED User
                    SELECT Tweet FILTER .author IN U
                );
            }
            type Tweet {
                property text -> str;
                link author -> User;
            }
        ''', module='default')

        await self.assert_query_result(
            r"""
                SELECT User {
                    name,
                    tweets: {
                        text
                    } ORDER BY .text,
                } ORDER BY .name;
            """,
            [{
                'name': 'Alice',
                'tweets': [{
                    'text': 'Hello'
                }, {
                    'text': 'Hi'
                }],
            }, {
                'name': 'Billie',
                'tweets': [{
                    'text': 'Hello'
                }, {
                    'text': 'Hi'
                }],
            }],
        )

        await self.migrate(r'''
            type User {
                property name -> str;
                multi link tweets := (
                    WITH U := User
                    SELECT U.<author[IS Tweet]
                );
            }
            type Tweet {
                property text -> str;
                link author -> User;
            }
        ''', module='default')

        await self.assert_query_result(
            r"""
                SELECT User {
                    name,
                    tweets: {
                        text
                    } ORDER BY .text,
                } ORDER BY .name;
            """,
            [{
                'name': 'Alice',
                'tweets': [{
                    'text': 'Hello'
                }],
            }, {
                'name': 'Billie',
                'tweets': [{
                    'text': 'Hi'
                }],
            }],
        )

        await self.migrate(r'''
            type User {
                property name -> str;
                multi link tweets := (
                    WITH User := DETACHED User
                    SELECT User.<author[IS Tweet]
                );
            }
            type Tweet {
                property text -> str;
                link author -> User;
            }
        ''', module='default')

        await self.assert_query_result(
            r"""
                SELECT User {
                    name,
                    tweets: {
                        text
                    } ORDER BY .text,
                } ORDER BY .name;
            """,
            [{
                'name': 'Alice',
                'tweets': [{
                    'text': 'Hello'
                }, {
                    'text': 'Hi'
                }],
            }, {
                'name': 'Billie',
                'tweets': [{
                    'text': 'Hello'
                }, {
                    'text': 'Hi'
                }],
            }],
        )

    @tb.ignore_warnings('more than one.* in a FILTER clause')
    async def test_edgeql_migration_computed_04(self):
        await self.migrate(r'''
            type User {
                property name -> str;
                multi property tweets := Tweet.text;
            }
            type Tweet {
                property text -> str;
                link author -> User;
            }
        ''', module='default')

        await self.con.execute("""
            INSERT Tweet {
                text := 'Hello',
                author := (
                    INSERT User {name := 'Alice'}
                )
            };
            INSERT Tweet {
                text := 'Hi',
                author := (
                    INSERT User {name := 'Billie'}
                )
            };
        """)

        # Validate our structures
        await self.assert_query_result(
            r"""
                SELECT Tweet {
                    text,
                    author: {
                        name
                    },
                } ORDER BY .text;
            """,
            [{
                'text': 'Hello',
                'author': {
                    'name': 'Alice'
                },
            }, {
                'text': 'Hi',
                'author': {
                    'name': 'Billie'
                },
            }],
        )

        await self.assert_query_result(
            r"""
                SELECT User {
                    name,
                    tweets,
                } ORDER BY .name;
            """,
            [{
                'name': 'Alice',
                'tweets': {'Hello', 'Hi'},
            }, {
                'name': 'Billie',
                'tweets': {'Hello', 'Hi'},
            }],
        )

        await self.migrate(r'''
            type User {
                property name -> str;
                multi property tweets := User.<author[IS Tweet].text;
            }
            type Tweet {
                property text -> str;
                link author -> User;
            }
        ''', module='default')

        await self.assert_query_result(
            r"""
                SELECT User {
                    name,
                    tweets,
                } ORDER BY .name;
            """,
            [{
                'name': 'Alice',
                'tweets': {'Hello'},
            }, {
                'name': 'Billie',
                'tweets': {'Hi'},
            }],
        )

        await self.migrate(r'''
            type User {
                property name -> str;
                multi property tweets := .<author[IS Tweet].text;
            }
            type Tweet {
                property text -> str;
                link author -> User;
            }
        ''', module='default')

        await self.assert_query_result(
            r"""
                SELECT User {
                    name,
                    tweets,
                } ORDER BY .name;
            """,
            [{
                'name': 'Alice',
                'tweets': {'Hello'},
            }, {
                'name': 'Billie',
                'tweets': {'Hi'},
            }],
        )

        await self.migrate(r'''
            type User {
                property name -> str;
                multi property tweets := (
                    SELECT Tweet FILTER .author = User
                ).text;
            }
            type Tweet {
                property text -> str;
                link author -> User;
            }
        ''', module='default')

        await self.assert_query_result(
            r"""
                SELECT User {
                    name,
                    tweets,
                } ORDER BY .name;
            """,
            [{
                'name': 'Alice',
                'tweets': {'Hello'},
            }, {
                'name': 'Billie',
                'tweets': {'Hi'},
            }],
        )

        await self.migrate(r'''
            type User {
                property name -> str;
                multi property tweets := (
                    WITH U := User
                    SELECT Tweet FILTER .author = U
                ).text;
            }
            type Tweet {
                property text -> str;
                link author -> User;
            }
        ''', module='default')

        await self.assert_query_result(
            r"""
                SELECT User {
                    name,
                    tweets,
                } ORDER BY .name;
            """,
            [{
                'name': 'Alice',
                'tweets': {'Hello'},
            }, {
                'name': 'Billie',
                'tweets': {'Hi'},
            }],
        )

        await self.migrate(r'''
            type User {
                property name -> str;
                multi property tweets := (
                    WITH U := DETACHED User
                    SELECT Tweet FILTER .author = U
                ).text;
            }
            type Tweet {
                property text -> str;
                link author -> User;
            }
        ''', module='default')

        await self.assert_query_result(
            r"""
                SELECT User {
                    name,
                    tweets,
                } ORDER BY .name;
            """,
            [{
                'name': 'Alice',
                'tweets': {'Hello', 'Hi'},
            }, {
                'name': 'Billie',
                'tweets': {'Hello', 'Hi'},
            }],
        )

        await self.migrate(r'''
            type User {
                property name -> str;
                multi property tweets := (
                    WITH U := User
                    SELECT U.<author[IS Tweet].text
                );
            }
            type Tweet {
                property text -> str;
                link author -> User;
            }
        ''', module='default')

        await self.assert_query_result(
            r"""
                SELECT User {
                    name,
                    tweets,
                } ORDER BY .name;
            """,
            [{
                'name': 'Alice',
                'tweets': {'Hello'},
            }, {
                'name': 'Billie',
                'tweets': {'Hi'},
            }],
        )

        await self.migrate(r'''
            type User {
                property name -> str;
                multi property tweets := (
                    WITH User := DETACHED User
                    SELECT User.<author[IS Tweet].text
                );
            }
            type Tweet {
                property text -> str;
                link author -> User;
            }
        ''', module='default')

        await self.assert_query_result(
            r"""
                SELECT User {
                    name,
                    tweets,
                } ORDER BY .name;
            """,
            [{
                'name': 'Alice',
                'tweets': {'Hello', 'Hi'},
            }, {
                'name': 'Billie',
                'tweets': {'Hello', 'Hi'},
            }],
        )

    async def test_edgeql_migration_computed_05(self):
        await self.migrate(r'''
            type Bar {
                multi link foo := Foo;
                property name -> str;
            };
            type Foo {
                link bar -> Bar;
                property val -> str;
            };
        ''', module='default')

        await self.con.execute("""
            INSERT Foo {
                val := 'foo0',
                bar := (
                    INSERT Bar {name := 'bar0'}
                ),
            };
            INSERT Foo {
                val := 'foo1',
                bar := (
                    INSERT Bar {name := 'bar1'}
                ),
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Foo {
                    val,
                    bar: {
                        name,
                        foo: {
                            val
                        } ORDER BY .val,
                    },
                } ORDER BY .val;
            """,
            [{
                'val': 'foo0',
                'bar': {
                    'name': 'bar0',
                    'foo': [{'val': 'foo0'}, {'val': 'foo1'}],
                },
            }, {
                'val': 'foo1',
                'bar': {
                    'name': 'bar1',
                    'foo': [{'val': 'foo0'}, {'val': 'foo1'}],
                },
            }],
        )

    async def test_edgeql_migration_computed_06(self):
        await self.migrate(r'''
            type Bar {
                multi property foo := Foo.val;
                property name -> str;
            };
            type Foo {
                link bar -> Bar;
                property val -> str;
            };
        ''', module='default')

        await self.con.execute("""
            INSERT Foo {
                val := 'foo0',
                bar := (
                    INSERT Bar {name := 'bar0'}
                ),
            };
            INSERT Foo {
                val := 'foo1',
                bar := (
                    INSERT Bar {name := 'bar1'}
                ),
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Foo {
                    val,
                    bar: {
                        name,
                        foo,
                    },
                } ORDER BY .val;
            """,
            [{
                'val': 'foo0',
                'bar': {
                    'name': 'bar0',
                    'foo': {'foo0', 'foo1'},
                },
            }, {
                'val': 'foo1',
                'bar': {
                    'name': 'bar1',
                    'foo': {'foo0', 'foo1'},
                },
            }],
        )

    async def test_edgeql_migration_computed_07(self):
        await self.migrate(r'''
            type T;
            type S {
                multi ts: T;
                val := count(.ts);
            };
        ''', module='default')
        await self.migrate(r'''
            type T;
            type S {
                multi ts: T;
                val := 0;
            };
        ''', module='default')
        await self.migrate(r'''
            type T;
            type S {
                multi ts: T;
                val := count(.ts);
            };
        ''', module='default')

    async def test_edgeql_migration_reject_prop_01(self):
        await self.migrate('''
            type User {
                property foo -> str;
            };
        ''')

        await self.start_migration('''
            type User {
                property bar -> str;
            };
        ''')

        await self.interact([
            ("did you rename property 'foo' of object type "
             "'test::User' to 'bar'?", "n"),
            # XXX: or should this be split up?
            "did you alter object type 'test::User'?"
        ])
        await self.fast_forward_describe_migration()

    async def test_edgeql_migration_reject_prop_02(self):
        await self.migrate('''
            type User {
                required property foo -> str;
            };
        ''')

        await self.start_migration('''
            type User {
                property bar -> str;
            };
        ''')

        # Initial confidence should *not* be 1.0 here
        res = json.loads(await self.con.query_single(
            'DESCRIBE CURRENT MIGRATION AS JSON;'))
        self.assertLess(res['proposed']['confidence'], 1.0)

        await self.interact([
            ("did you rename property 'foo' of object type 'test::User' to "
             "'bar'?", "n"),
            # XXX: or should this be split up?
            "did you alter object type 'test::User'?"
        ])
        await self.fast_forward_describe_migration()

    async def test_edgeql_migration_reject_prop_03(self):
        await self.migrate('''
            type User {
                required property foo -> str;
            };
        ''')

        await self.start_migration('''
            type User {
                required property bar -> int64;
            };
        ''')

        await self.interact([
            # Or should this be split into rename and reset optionality?
            ("did you create property 'bar' of object type 'test::User'?",
             "n"),
            ("did you rename property 'foo' of object type 'test::User' to "
             "'bar'?"),
            ("did you alter the type of property 'bar' of object type "
             "'test::User'?",
             "y",
             "<int64>.bar"),
        ])
        await self.fast_forward_describe_migration()

    async def test_edgeql_migration_reject_prop_04(self):
        await self.migrate('''
            type Foo;
            type Bar;
        ''')

        await self.start_migration('''
            type Foo;
            type Bar extending Foo;
        ''')

        await self.interact([
            ("did you alter object type 'test::Bar'?", "n"),
            "did you drop object type 'test::Bar'?",
            "did you create object type 'test::Bar'?",
        ])
        await self.fast_forward_describe_migration()

    @test.xerror('Fails to rebase because the type is mismatched')
    async def test_edgeql_migration_reject_prop_05(self):
        await self.migrate('''
            scalar type Slug extending str;
            abstract type Named {
                required property name -> Slug;
            };
            type User {
                required property name -> str;
            };
        ''')

        await self.start_migration('''
            scalar type Slug extending str;
            abstract type Named {
                required property name -> Slug;
            };
            type User extending Named;
        ''')

        await self.interact([
            ("did you drop property 'name' of object type 'test::User'?", "n"),
        ], check_complete=False)
        await self.fast_forward_describe_migration()

    async def test_edgeql_migration_vector_change_01(self):
        await self.migrate('''
            using extension pgvector;
            module default {
                scalar type Embedding extending ext::pgvector::vector<384>;
                type Obj {
                   embeddings: Embedding;
                }
            }
        ''', explicit_modules=True)

        await self.start_migration('''
            using extension pgvector;
            module default {
                scalar type Embedding extending ext::pgvector::vector<768>;
                type Obj {
                   embeddings: Embedding;
                }
            }
        ''', explicit_modules=True)

        await self.assert_describe_migration({
            'proposed': {
                'statements': [{
                    'text': """
                        ALTER TYPE default::Obj {DROP PROPERTY embeddings;};
                    """
                }]
            }
        })

        async with self.assertRaisesRegexTx(
            edgedb.SchemaDefinitionError,
            r"cannot produce migration because of a dependency cycle",
        ):
            await self.interact([
                ("did you drop property 'embeddings' of object type "
                 "'default::Obj'?", "n"),
            ])

    async def test_edgeql_migration_force_delete_01(self):
        await self.migrate('''
            type Base;
            type Foo;
            type Bar { link foo -> Foo; };
        ''')

        await self.start_migration('''
            type Base;
            type Foo extending Base;
            type Bar { link foo -> Foo; };
        ''')

        await self.interact([
            ("did you alter object type 'test::Foo'?", "n"),
            "did you drop link 'foo' of object type 'test::Bar'?"
        ], check_complete=False)
        await self.fast_forward_describe_migration()

    async def test_edgeql_migration_force_delete_02(self):
        await self.migrate('''
            type Base;
            type Foo;
            type Bar extending Foo;
        ''')

        await self.start_migration('''
            type Base;
            type Foo extending Base;
            type Bar extending Foo;
        ''')

        await self.interact([
            ("did you alter object type 'test::Foo'?", "n"),
            "did you drop object type 'test::Bar'?"
        ], check_complete=False)
        await self.fast_forward_describe_migration()

    async def test_edgeql_migration_eq_01(self):
        await self.migrate("""
            type Base;
        """)
        await self.con.execute("""
            SET MODULE test;

            INSERT Base;
        """)

        # Try altering the schema to a state inconsistent with current
        # data.
        with self.assertRaisesRegex(
            AssertionError,
            r"Please specify an expression to populate existing objects "
            r"in order to make property 'name' of object type 'test::Base' "
            r"required"
        ):
            await self.migrate("""
                type Base {
                    required property name -> str;
                }
            """)
        # Migration without making the property required.
        await self.migrate("""
            type Base {
                property name -> str;
            }
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    name
                };
            """,
            [{
                'name': None,
            }],
        )

        await self.con.execute("""
            UPDATE
                Base
            SET {
                name := 'base_01'
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    name
                };
            """,
            [{
                'name': 'base_01',
            }],
        )

        # Inherit from the Base, making name required.
        await self.migrate("""
            type Base {
                property name -> str;
            }

            type Derived extending Base {
                overloaded required property name -> str;
            }
        """)
        await self.con.execute("""
            INSERT Derived {
                name := 'derived_01'
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Base.name;
            """,
            {'base_01', 'derived_01'},
        )

    async def test_edgeql_migration_eq_02(self):
        await self.migrate(r"""
            type Base {
                property foo -> str;
            }

            type Derived extending Base {
                overloaded required property foo -> str;
            }
        """)
        await self.con.execute("""
            SET MODULE test;

            INSERT Base {
                foo := 'base_02',
            };
            INSERT Derived {
                foo := 'derived_02',
            };
        """)

        await self.migrate(r"""
            type Base {
                # rename 'foo'
                property foo2 -> str;
            }

            type Derived extending Base {
                overloaded required property foo2 -> str;
            }
        """)

        # the data still persists
        await self.assert_query_result(
            r"""
                SELECT Base {
                    __type__: {name},
                    foo2,
                } ORDER BY .foo2;
            """,
            [{
                '__type__': {'name': 'test::Base'},
                'foo2': 'base_02',
            }, {
                '__type__': {'name': 'test::Derived'},
                'foo2': 'derived_02',
            }],
        )

    async def test_edgeql_migration_eq_03(self):
        await self.migrate(r"""
            type Base {
                property foo -> str;
            }

            type Derived extending Base {
                overloaded required property foo -> str;
            }
        """)
        await self.con.execute("""
            SET MODULE test;

            INSERT Base {
                foo := 'base_03',
            };
            INSERT Derived {
                foo := 'derived_03',
            };
        """)

        await self.migrate(r"""
            type Base;
                # drop 'foo'

            type Derived extending Base {
                # completely different property
                property foo2 -> str;
            }
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    __type__: {name},
                    [IS Derived].foo2,
                } ORDER BY .foo2;
            """,
            [{
                '__type__': {'name': 'test::Base'},
                'foo2': None,
            }, {
                '__type__': {'name': 'test::Derived'},
                'foo2': None,
            }],
        )

    async def test_edgeql_migration_eq_04(self):
        await self.migrate(r"""
            type Base {
                property foo -> str;
            }

            type Derived extending Base;

            type Further extending Derived {
                overloaded required property foo -> str;
            }
        """)
        await self.con.execute("""
            SET MODULE test;

            INSERT Base {
                foo := 'base_04',
            };
            INSERT Derived {
                foo := 'derived_04',
            };
            INSERT Further {
                foo := 'further_04',
            };
        """)

        await self.migrate(r"""
            type Base;
                # drop 'foo'

            type Derived extending Base;

            type Further extending Derived {
                # completely different property
                property foo2 -> str;
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    __type__: {name},
                    [IS Further].foo2,
                } ORDER BY .__type__.name;
            """,
            [{
                '__type__': {'name': 'test::Base'},
                'foo2': None,
            }, {
                '__type__': {'name': 'test::Derived'},
                'foo2': None,
            }, {
                '__type__': {'name': 'test::Further'},
                'foo2': None,
            }],
        )

    async def test_edgeql_migration_eq_06(self):
        await self.migrate(r"""
            type Base {
                property foo -> int64;
            }

            type Derived extending Base {
                overloaded required property foo -> int64;
            }
        """)
        await self.con.execute("""
            SET MODULE test;

            INSERT Base {
                foo := 6,
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    __type__: {name},
                    foo,
                };
            """,
            [{
                '__type__': {'name': 'test::Base'},
                # the value was correctly inserted
                'foo': 6,
            }],
        )

        await self.migrate(r"""
            type Base {
                property foo -> float64;
            }

            type Derived extending Base {
                overloaded required property foo -> float64;
            }
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    __type__: {name},
                    foo,
                };
            """,
            [{
                '__type__': {'name': 'test::Base'},
                'foo': 6.0,
            }],
        )

    async def test_edgeql_migration_eq_07(self):
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.migrate(r"""
            type Child {
                required property name -> str {
                    constraint exclusive;
                }
            }

            type Base {
                required property name -> str;
                link bar -> Child;
            }
        """)

        await self.con.execute('''
            INSERT Child { name := 'c1' };
            INSERT Child { name := 'c2' };

            INSERT Base {
                name := 'b1',
                bar := (SELECT Child FILTER .name = 'c1'),
            };

            INSERT Base {
                name := 'b2',
            };
        ''')

        await self.assert_query_result(
            r"""
                SELECT Base {
                    bar: {
                        name
                    }
                } ORDER BY .name;
            """,
            [{
                'bar': {
                    'name': 'c1',
                },
            }, {
                'bar': None,
            }],
        )

        await self.migrate(r"""
            type Child {
                required property name -> str {
                    constraint exclusive;
                }
            }

            type Base {
                required property name -> str;
                required link bar -> Child {
                    # add a constraint
                    constraint exclusive;
                }
            }
        """, user_input=[
            "SELECT Child FILTER .name = 'c2'"
        ])

        await self.assert_query_result(
            r"""
                SELECT Base {
                    bar: {
                        name
                    }
                } ORDER BY .name;
            """,
            [{
                'bar': {
                    'name': 'c1',
                },
            }, {
                'bar': {
                    'name': 'c2',
                },
            }],
        )

    async def test_edgeql_migration_eq_08(self):
        await self.migrate(r"""
            type Base {
                property foo -> str;
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {
                foo := 'very_long_test_str_base_08',
            };
        """)

        # Try altering the schema to a state inconsistent with current
        # data.
        new_state = r"""
            type Base {
                property foo -> str {
                    # add a constraint
                    constraint max_len_value(10);
                }
            }
        """
        with self.assertRaisesRegex(
                edgedb.ConstraintViolationError,
                r"foo must be no longer than 10 characters"):
            await self.migrate(new_state)

        # Fix the data.
        await self.con.execute(r"""
            UPDATE Base
            SET {
                foo := 'base_08',
            };
        """)

        # Migrate to same state as before now that the data is fixed.
        await self.migrate(new_state)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo,
                };
            """,
            [{
                'foo': 'base_08',
            }],
        )

    async def test_edgeql_migration_eq_09(self):
        await self.migrate(r"""
            scalar type constraint_length extending str {
                constraint max_len_value(10);
            }
            type Base {
                property foo -> constraint_length;
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {
                foo := 'b09',
            };
        """)

        # Try altering the schema to a state inconsistent with current
        # data.
        new_state = r"""
            scalar type constraint_length extending str {
                constraint max_len_value(10);
                # add a constraint
                constraint min_len_value(5);
            }
            type Base {
                property foo -> constraint_length;
            }
        """
        with self.assertRaisesRegex(
                edgedb.ConstraintViolationError,
                r'Existing test::Base\.foo values violate the new constraint'):
            await self.migrate(new_state)

        # Fix the data.
        await self.con.execute(r"""
            UPDATE Base
            SET {
                foo := 'base_09',
            };
        """)

        # Migrate to same state as before now that the data is fixed.
        await self.migrate(new_state)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo,
                };
            """,
            [{
                'foo': 'base_09',
            }],
        )

    async def test_edgeql_migration_eq_11(self):
        await self.migrate(r"""
            type Base {
                property foo -> str;
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {
                foo := 'base_11',
            };
        """)

        await self.migrate(r"""
            type Child;

            type Base {
                # change property to link with same name
                link foo -> Child {
                    # add a constraint
                    constraint exclusive;
                }
            }
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo,
                };
            """,
            [{
                'foo': None,
            }],
        )

    async def test_edgeql_migration_eq_12(self):
        await self.migrate(r"""
            type Child;

            type Base {
                property foo -> str {
                    constraint exclusive;
                }

                link bar -> Child {
                    constraint exclusive;
                }
            }
        """)
        await self.con.execute("""
            SET MODULE test;
        """)
        data = await self.con.query(r"""
            SELECT (
                INSERT Base {
                    foo := 'base_12',
                    bar := (INSERT Child)
                })
            {
                foo,
                bar: {id}
            };
        """)

        await self.migrate(r"""
            type Child;

            type Base {
                # drop constraints
                property foo -> str;
                link bar -> Child;
            }
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo,
                    bar: {id}
                };
            """,
            [{
                'foo': 'base_12',
                'bar': {'id': data[0].bar.id}
            }],
        )

    async def test_edgeql_migration_eq_13(self):
        await self.migrate(r"""
            type Child;

            type Base {
                link bar -> Child;
            }

            type Derived extending Base {
                overloaded required link bar -> Child;
            }
        """)
        await self.con.execute("""
            SET MODULE test;
        """)
        data = await self.con.query(r"""
            SELECT (
                INSERT Derived {
                    bar := (INSERT Child)
                })
            {
                bar: {id}
            };
        """)

        await self.migrate(r"""
            type Child;

            type Base;
                # drop 'bar'

            type Derived extending Base {
                # no longer inherit link 'bar'
                link bar -> Child;
            }
        """)

        await self.assert_query_result(
            r"""
                SELECT Derived {
                    bar: {id}
                };
            """,
            [{
                'bar': {'id': data[0].bar.id}
            }],
        )

    async def test_edgeql_migration_eq_14a(self):
        await self.migrate(r"""
            type Base;

            type Derived extending Base {
                property foo -> str;
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Derived {
                foo := 'derived_14',
            };
        """)

        await self.migrate(r"""
            type Base {
                # move the property earlier in the inheritance
                property foo -> str;
            }

            type Derived extending Base {
                overloaded property foo -> str {
                    annotation title := 'overloaded'
                }
            }
        """)

        await self.assert_query_result(
            r"""
                SELECT Derived {
                    foo,
                };
            """,
            [{
                'foo': 'derived_14',
            }],
        )

    async def test_edgeql_migration_eq_14b(self):
        # Same as above, except POPULATE and inspect the query
        await self.migrate(r"""
            type Base;

            type Derived extending Base {
                property foo -> str;
            }
        """)

        await self.start_migration(r"""
            type Base {
                # move the property earlier in the inheritance
                property foo -> str;
            }

            type Derived extending Base {
                overloaded required property foo -> str;
            }
        """, populate=True)

        await self.assert_describe_migration({
            'confirmed': ["""
                ALTER TYPE test::Base {
                    CREATE PROPERTY foo: std::str;
                };
            """, """
                ALTER TYPE test::Derived {
                    ALTER PROPERTY foo {
                        SET REQUIRED;
                    };
                };
            """],
            'complete': True,
        })

    async def test_edgeql_migration_eq_16(self):
        await self.migrate(r"""
            type Child;

            type Base;

            type Derived extending Base {
                link bar -> Child;
            }
        """)
        await self.con.execute("""
            SET MODULE test;
        """)
        data = await self.con.query(r"""
            SELECT (
                INSERT Derived {
                    bar := (INSERT Child),
                }
            ) {
                bar: {id}
            };
        """)

        await self.migrate(r"""
            type Child;

            type Base {
                # move the link earlier in the inheritance
                link bar -> Child;
            }

            type Derived extending Base;
        """)

        await self.assert_query_result(
            r"""
                SELECT Derived {
                    bar,
                };
            """,
            [{
                'bar': {'id': data[0].bar.id},
            }],
        )

        await self.migrate(
            r"""
                type Child;

                type Base {
                    link bar -> Child;
                }

                type Derived extending Base {
                    # also make the link 'required'
                    overloaded required link bar -> Child;
                }
            """,
            user_input=[
                '.bar',
            ],
        )

        await self.assert_query_result(
            r"""
                SELECT Derived {
                    bar,
                };
            """,
            [{
                'bar': {'id': data[0].bar.id},
            }],
        )

    async def test_edgeql_migration_eq_18a(self):
        await self.migrate(r"""
            type Base {
                property name := 'computable'
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base;
        """)

        await self.migrate(r"""
            type Base {
                # change a property from a computable to regular with a default
                property name -> str {
                    default := 'something'
                }
            }
        """)

        # Insert a new object, this one should have a new default name.
        await self.con.execute(r"""
            INSERT Base;
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    name,
                } ORDER BY .name EMPTY LAST;
            """,
            [{
                'name': 'something',
            }, {
                'name': 'something',
            }],
        )

    async def test_edgeql_migration_eq_18b(self):
        await self.migrate(r"""
            type Base {
                property name := 'computable'
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base;
        """)

        await self.migrate(r"""
            type Base {
                # change a property from a computable to regular with a default
                property name -> str {
                    default := <str>count(Object)
                }
            }
        """)

        # Insert a new object, this one should have a new default name.
        await self.con.execute(r"""
            INSERT Base;
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    name,
                } ORDER BY .name EMPTY LAST;
            """,
            [{
                'name': str,
            }, {
                'name': str,
            }],
        )

    async def test_edgeql_migration_eq_18c(self):
        await self.migrate(r"""
            type Base {
                property name := 'computable'
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base;
        """)

        await self.migrate(r"""
            type Base {
                # change a property from a computable to regular with a default
                required property name -> str {
                    default := <str>count(Object)
                }
            }
        """)

        # Insert a new object, this one should have a new default name.
        await self.con.execute(r"""
            INSERT Base;
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    name,
                } ORDER BY .name EMPTY LAST;
            """,
            [{
                'name': str,
            }, {
                'name': str,
            }],
        )

    async def test_edgeql_migration_eq_19(self):
        await self.migrate(r"""
            type Base {
                property name -> str
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {
                name := 'base_19'
            };
        """)

        await self.migrate(r"""
            type Base {
                # change a regular property to a computable
                property name := 'computable'
            }
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    name,
                };
            """,
            [{
                'name': 'computable',
            }],
        )

    async def test_edgeql_migration_eq_21(self):
        await self.migrate(r"""
            type Base {
                property foo -> str;
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {
                foo := 'base_21'
            };
        """)

        await self.migrate(r"""
            type Base {
                property foo -> str;
                # add a property
                property bar -> int64;
            }
        """)

        await self.con.execute(r"""
            UPDATE Base
            SET {
                bar := 21
            };
        """)
        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo,
                    bar
                };
            """,
            [{
                'foo': 'base_21',
                'bar': 21,
            }],
        )

        await self.migrate(r"""
            type Base {
                # make the old property into a computable
                property foo := <str>__source__.bar;
                property bar -> int64;
            }
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo,
                    bar
                };
            """,
            [{
                'foo': '21',
                'bar': 21,
            }],
        )

    async def test_edgeql_migration_eq_22(self):
        await self.migrate(r"""
            type Base {
                property foo -> str;
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {
                foo := 'base_22'
            };
        """)

        await self.migrate(r"""
            # rename the type, although this test doesn't ensure that
            # renaming actually took place
            type NewBase {
                property foo -> str;
            }
        """)

        await self.assert_query_result(
            r"""
                SELECT NewBase {
                    foo,
                };
            """,
            [{
                'foo': 'base_22',
            }],
        )

        await self.migrate(r"""
            type NewBase {
                property foo -> str;
                # add a property
                property bar -> int64;
            }
        """)

        await self.con.execute(r"""
            UPDATE NewBase
            SET {
                bar := 22
            };
        """)
        await self.assert_query_result(
            r"""
                SELECT NewBase {
                    foo,
                    bar
                };
            """,
            [{
                'foo': 'base_22',
                'bar': 22,
            }],
        )

        await self.migrate(r"""
            type NewBase {
                # drop 'foo'
                property bar -> int64;
            }

            # add a alias to emulate the original
            alias Base := (
                SELECT NewBase {
                    foo := <str>.bar
                }
            );
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo,
                };
            """,
            [{
                'foo': '22',
            }],
        )

    async def test_edgeql_migration_eq_23(self):
        await self.migrate(r"""
            type Child {
                property foo -> str;
            }

            type Base {
                link bar -> Child;
            }

            alias Alias01 := (
                SELECT Base {
                    child_foo := .bar.foo
                }
            );
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {
                bar := (
                    INSERT Child {
                        foo := 'child_23'
                    }
                )
            };
        """)

        await self.migrate(r"""
            type Child {
                property foo -> str;
            }

            # exchange a type for a alias
            alias Base := (
                SELECT Child {
                    # bar is the same as the root object
                    bar := Child
                }
            );

            alias Alias01 := (
                # now this alias refers to another alias
                SELECT Base {
                    child_foo := .bar.foo
                }
            );
        """)

        await self.assert_query_result(
            r"""
                SELECT Alias01 {
                    child_foo,
                };
            """,
            [{
                'child_foo': 'child_23',
            }],
        )

    async def test_edgeql_migration_eq_24(self):
        await self.migrate(r"""
            type Child;

            type Base {
                link bar -> Child;
            }
        """)
        await self.con.execute("""
            SET MODULE test;
        """)
        data = await self.con.query(r"""
            SELECT (
                INSERT Base {
                    bar := (INSERT Child)
                }
            ) {
                bar: {id}
            }
        """)

        await self.migrate(r"""
            type Child;

            type Base {
                # increase link cardinality
                multi link bar -> Child;
            }
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    bar: {id},
                };
            """,
            [{
                'bar': [{'id': data[0].bar.id}],
            }],
        )

    async def test_edgeql_migration_eq_25(self):
        await self.migrate(r"""
            type Child;

            type Base {
                multi link bar -> Child;
            }
        """)
        await self.con.execute("""
            SET MODULE test;
        """)
        data = await self.con.query(r"""
            SELECT (
                INSERT Base {
                    bar := (INSERT Child)
                }
            ) {
                bar: {id}
            }
        """)

        await self.migrate(r"""
            type Child;

            type Base {
                # reduce link cardinality
                link bar -> Child;
            }
        """, user_input=[
            '(SELECT .bar LIMIT 1)'
        ])

        await self.assert_query_result(
            r"""
                SELECT Base {
                    bar: {id},
                };
            """,
            [{
                'bar': {'id': data[0].bar[0].id},
            }],
        )

        await self.migrate(r"""
            type Child;

            type Base {
                link bar -> Child {
                    # further restrict the link
                    constraint exclusive
                }
            }
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    bar: {id},
                };
            """,
            [{
                'bar': {'id': data[0].bar[0].id},
            }],
        )

    async def test_edgeql_migration_eq_26(self):
        await self.migrate(r"""
            type Child;

            type Parent {
                link bar -> Child;
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Parent {
                bar := (INSERT Child)
            };
        """)

        await self.migrate(r"""
            type Child;

            type Parent {
                link bar -> Child;
            }

            # derive a type
            type DerivedParent extending Parent;
        """)

        await self.assert_query_result(
            r"""
                SELECT Parent {
                    type := .__type__.name,
                    bar_type := .bar.__type__.name
                };
            """,
            [{
                'type': 'test::Parent',
                'bar_type': 'test::Child',
            }],
        )

        await self.migrate(
            r"""
                type Child;

                type DerivedChild extending Child;

                type Parent {
                    link bar -> Child;
                }

                type DerivedParent extending Parent;
            """,
        )

        await self.migrate(
            r"""
                type Child;

                type DerivedChild extending Child;

                type Parent {
                    link bar -> Child;
                }

                # derive a type with a more restrictive link
                type DerivedParent extending Parent {
                    overloaded link bar -> DerivedChild;
                }
            """,
            user_input=[".bar[IS DerivedChild]"],
        )

        await self.con.execute(r"""
            INSERT DerivedParent {
                bar := (INSERT DerivedChild)
            }
        """)
        await self.assert_query_result(
            r"""
                SELECT Parent {
                    type := .__type__.name,
                    bar_type := .bar.__type__.name
                } ORDER BY .bar_type;
            """,
            [{
                'type': 'test::Parent',
                'bar_type': 'test::Child',
            }, {
                'type': 'test::DerivedParent',
                'bar_type': 'test::DerivedChild',
            }],
        )

    async def test_edgeql_migration_eq_27(self):
        await self.migrate(r"""
            abstract type Named {
                property name -> str;
            }

            type Foo extending Named;
            type Bar extending Named;
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Foo {
                name := 'foo_27',
            };
            INSERT Bar {
                name := 'bar_27',
            };
        """)

        await self.migrate(r"""
            abstract type Named {
                property name -> str;
            }

            # the types stop extending named, but retain the property
            # 'name'
            type Foo {
                property name -> str;
            };

            type Bar {
                property name -> str;
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Foo.name;
            """,
            [
                'foo_27',
            ],
        )
        await self.assert_query_result(
            r"""
                SELECT Bar.name;
            """,
            [
                'bar_27',
            ],
        )

        await self.migrate(r"""
            abstract type Named {
                property name -> str;
            }

            type Foo {
                property name -> str;
            };

            type Bar {
                # rename 'name' to 'title'
                property title -> str;
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Foo.name;
            """,
            [
                'foo_27',
            ],
        )
        await self.assert_query_result(
            r"""
                SELECT Bar.title;
            """,
            [
                'bar_27',
            ],
        )

    async def test_edgeql_migration_eq_29(self):
        await self.migrate(r"""
            type Child {
                property foo -> str;
            }

            alias Base := (
                SELECT Child {
                    bar := .foo
                }
            );
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Child {
                foo := 'child_29',
            };
        """)

        await self.migrate(r"""
            # drop everything
        """)

    async def test_edgeql_migration_eq_30(self):
        await self.migrate(r"""
            type Foo {
                property name -> str;
            };

            type Bar {
                property title -> str;
            };
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Foo {
                name := 'foo_30',
            };
            INSERT Bar {
                title := 'bar_30',
            };
        """)

        await self.migrate(r"""
            type Foo {
                property name -> str;
            };

            type Bar {
                # rename 'title' to 'name'
                property name -> str;
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Foo.name;
            """,
            [
                'foo_30',
            ],
        )
        await self.assert_query_result(
            r"""
                SELECT Bar.name;
            """,
            [
                'bar_30',
            ],
        )

        await self.migrate(r"""
            # both types have a name, so the name prop is factored out
            # into a more basic type.
            abstract type Named {
                property name -> str;
            }

            type Foo extending Named;
            type Bar extending Named;
        """)

        await self.assert_query_result(
            r"""
                SELECT Foo.name;
            """,
            [
                'foo_30',
            ],
        )
        await self.assert_query_result(
            r"""
                SELECT Bar.name;
            """,
            [
                'bar_30',
            ],
        )

    async def test_edgeql_migration_eq_31(self):
        # Issue 727.
        #
        # Starting with the sample schema (from frontpage) migrate to
        # a schema with only type User.
        await self.migrate(r"""
            # This is an abstract object containing
            # text.
            abstract type Text {
              required property body -> str {
                # Maximum length of text is 10000
                # characters.
                constraint max_len_value(10000);
              }
            }

            type User {
              required property name -> str;
            }

            abstract type Owned {
              # By default links are optional.
              required link owner -> User;
            }

            # UniquelyNamed is a an abstract type that
            # enforces name uniqueness across all
            # instances of its subtype.
            abstract type UniquelyNamed {
              required property name -> str {
                delegated constraint exclusive;
              }
            }

            type Status extending UniquelyNamed;

            type Priority extending UniquelyNamed;

            # LogEntry is an Owned and a Text,
            # so it will have all of their links
            # and properties, in particular, the
            # "owner" link and the "body" property.
            type LogEntry extending Owned, Text {
              required property spent_time -> int64;
            }

            type Comment extending Text, Owned {
              required link issue -> Issue;
              link parent -> Comment;
            }
            # issue_num_t is defined as a concrete
            # sequence type, used to generate
            # sequential issue numbers.
            scalar type issue_num_t extending sequence;

            type Issue extending Owned, Text {
              required property title -> str;

              required property number -> issue_num_t {
                # The number values are automatically
                # generated, and are not supposed to be
                # directly writable.
                readonly := true;
              }

              property time_estimate -> int64;

              property start_date -> datetime {
                # The default value of start_date will be a
                # result of the EdgeQL expression above.
                default := (SELECT datetime_current());
              }

              property due_date -> datetime;

              required link status -> Status;

              link priority -> Priority;

              # The watchers link is mapped to User
              # type in many-to-many relation.
              multi link watchers -> User;

              multi link time_spent_log -> LogEntry {
                # Exclusive multi-link represents
                # a one-to-many relation.
                constraint exclusive;
              }

              multi link related_to -> Issue;
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Status {
                name := 'Open'
            };
            INSERT Status {
                name := 'Closed'
            };

            INSERT User {
                name := 'cosmophile'
            };
        """)

        await self.migrate(r"""
            type User {
              required property name -> str;
            }
        """)

        # there's only the User left
        await self.assert_query_result(
            r"""
                SELECT User.name;
            """,
            [
                'cosmophile',
            ],
        )

    async def test_edgeql_migration_eq_32(self):
        # Issue 727.
        #
        # Starting with a small schema migrate to remove its elements.
        # There are non-zero default Objects existing in a fresh blank
        # database because of placeholder objects used for GraphQL.
        start_objects = await self.con.query_single(r"""
            SELECT count(Object);
        """)

        await self.migrate(r"""
            type LogEntry {
              required property spent_time -> int64;
            }
            type Issue {
              multi link time_spent_log -> LogEntry {
                constraint exclusive;
              }
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT LogEntry {
                spent_time := 100
            };

            INSERT Issue {
                time_spent_log := LogEntry
            };
        """)

        await self.migrate(r"""
            type LogEntry {
              required property spent_time -> int64;
            }
        """)

        # there's only the LogEntry left
        await self.assert_query_result(
            r"""
                SELECT LogEntry.spent_time;
            """,
            [
                100,
            ],
        )
        await self.assert_query_result(
            r"""
                SELECT count(Object);
            """,
            [
                start_objects + 1,
            ],
        )

        await self.migrate(r"""
            # empty schema
        """)

        # no more additional objects
        await self.assert_query_result(
            r"""
                SELECT count(Object);
            """,
            [
                start_objects,
            ],
        )

    async def test_edgeql_migration_eq_33(self):
        await self.migrate(r"""
            type Child;

            type Base {
                link foo -> Child;
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Child;
            INSERT Base {
                foo := (SELECT Child LIMIT 1)
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo: {
                        __type__: {name},
                    }
                };
            """,
            [{
                'foo': {
                    '__type__': {'name': 'test::Child'},
                }
            }],
        )

        await self.migrate(
            r"""
                type Child;
                type Child2;

                type Base {
                    link foo -> Child;
                }
            """,
        )

        await self.migrate(
            r"""
                type Child;
                type Child2;

                type Base {
                    # change link type
                    link foo -> Child2;
                }
            """,
            user_input=[
                '.foo[IS Child2]'
            ],
        )

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo: {
                        __type__: {name},
                    }
                };
            """,
            [{
                # the link is empty because the target was changed
                'foo': None
            }],
        )

        await self.con.execute(r"""
            INSERT Child2;

            UPDATE Base
            SET {
                foo := (SELECT Child2 LIMIT 1)
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo: {
                        __type__: {name},
                    }
                };
            """,
            [{
                'foo': {
                    '__type__': {'name': 'test::Child2'},
                }
            }],
        )

    async def test_edgeql_migration_eq_34(self):
        # this is the reverse of test_edgeql_migration_eq_11
        await self.migrate(r"""
            type Child;

            type Base {
                link foo -> Child {
                    constraint exclusive;
                }
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Child;
            INSERT Base {
                foo := (SELECT Child LIMIT 1)
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo: {
                        __type__: {name},
                    }
                };
            """,
            [{
                'foo': {
                    '__type__': {'name': 'test::Child'},
                }
            }],
        )

        await self.migrate(r"""
            type Base {
                # change link to property with same name
                property foo -> str;
            }
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo
                };
            """,
            [{
                # the property is empty now
                'foo': None
            }],
        )

        await self.con.execute(r"""
            UPDATE Base
            SET {
                foo := 'base_foo_34'
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo
                };
            """,
            [{
                'foo': 'base_foo_34'
            }],
        )

    async def test_edgeql_migration_eq_35(self):
        await self.migrate(r"""
            type Child {
                required property name -> str;
            }

            type Base {
                multi link foo := (
                    SELECT Child FILTER .name = 'computable_35'
                )
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Child {
                name := 'computable_35'
            };
            INSERT Base;
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo: {
                        name
                    },
                };
            """,
            [{
                'foo': [{
                    'name': 'computable_35',
                }]
            }]
        )

        await self.migrate(r"""
            type Child {
                required property name -> str;
            }

            type Base {
                # change a link from a computable to regular
                multi link foo -> Child;
            }
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo: {
                        name
                    },
                };
            """,
            [{
                'foo': []
            }]
        )

        # Make sure that the new 'foo' can be updated.
        await self.con.execute(r"""
            INSERT Child {
                name := 'child_35'
            };
            UPDATE Base
            SET {
                foo := (
                    SELECT Child FILTER .name = 'child_35'
                )
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo: {
                        name
                    },
                };
            """,
            [{
                'foo': [{
                    'name': 'child_35'
                }]
            }]
        )

    async def test_edgeql_migration_eq_36(self):
        await self.migrate(r"""
            type Child {
                required property name -> str;
            }

            type Base {
                multi link foo -> Child;
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Child {
                name := 'computable_36'
            };
            INSERT Child {
                name := 'child_36'
            };
            INSERT Base {
                foo := (
                    SELECT Child FILTER .name = 'child_36'
                )
            };
        """)

        await self.migrate(r"""
            type Child {
                required property name -> str;
            }

            type Base {
                # change a regular link to a computable
                link foo := (
                    SELECT Child FILTER .name = 'computable_36'
                    LIMIT 1
                )
            }
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo: {
                        name
                    },
                };
            """,
            [{
                'foo': {
                    'name': 'computable_36'
                }
            }]
        )

    async def test_edgeql_migration_eq_37(self):
        # testing schema alias
        await self.migrate(r"""
            type Base;

            alias BaseAlias := (
                SELECT Base {
                    foo := 'base_alias_37'
                }
            )
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base;
        """)

        await self.assert_query_result(
            r"""
                SELECT BaseAlias {
                    foo
                };
            """,
            [{
                'foo': 'base_alias_37'
            }]
        )

        await self.migrate(r"""
            type Base;

            alias BaseAlias := (
                SELECT Base {
                    # "rename" a computable, since the value is given and
                    # not stored, this is no different from dropping
                    # original and creating a new property
                    foo2 := 'base_alias_37'
                }
            )
        """)

        await self.assert_query_result(
            r"""
                SELECT BaseAlias {
                    foo2
                };
            """,
            [{
                'foo2': 'base_alias_37'
            }]
        )

        with self.assertRaisesRegex(
                edgedb.InvalidReferenceError,
                r"object type 'test::Base' has no link or property 'foo'"):
            await self.con.execute(r"""
                SELECT BaseAlias {
                    foo
                };
            """)

    async def test_edgeql_migration_eq_38(self):
        # testing schema alias
        await self.migrate(r"""
            type Base;

            alias BaseAlias := (
                SELECT Base {
                    foo := 'base_alias_38'
                }
            )
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base;
        """)

        await self.assert_query_result(
            r"""
                SELECT BaseAlias {
                    foo
                };
            """,
            [{
                'foo': 'base_alias_38'
            }]
        )

        await self.migrate(r"""
            type Base;

            alias BaseAlias := (
                SELECT Base {
                    # keep the name, but change the type
                    foo := 38
                }
            )
        """)

        await self.assert_query_result(
            r"""
                SELECT BaseAlias {
                    foo
                };
            """,
            [{
                'foo': 38
            }]
        )

    async def test_edgeql_migration_eq_39(self):
        # testing schema alias
        await self.migrate(r"""
            type Base;

            type Foo {
                property name -> str
            }

            alias BaseAlias := (
                SELECT Base {
                    foo := (SELECT Foo FILTER .name = 'base_alias_39')
                }
            )
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base;
            INSERT Foo {name := 'base_alias_39'};
        """)

        await self.assert_query_result(
            r"""
                SELECT BaseAlias {
                    foo: {
                        name
                    }
                };
            """,
            [{
                'foo': [{
                    'name': 'base_alias_39'
                }]
            }]
        )

        await self.migrate(r"""
            type Base;

            type Foo {
                property name -> str
            }

            alias BaseAlias := (
                SELECT Base {
                    # "rename" a computable, since the value is given and
                    # not stored, this is no different from dropping
                    # original and creating a new multi-link
                    foo2 := (SELECT Foo FILTER .name = 'base_alias_39')
                }
            )
        """)

        await self.assert_query_result(
            r"""
                SELECT BaseAlias {
                    foo2: {
                        name
                    }
                };
            """,
            [{
                'foo2': [{
                    'name': 'base_alias_39'
                }]
            }]
        )

        with self.assertRaisesRegex(
                edgedb.InvalidReferenceError,
                r"object type 'test::Base' has no link or property 'foo'"):
            await self.con.execute(r"""
                SELECT BaseAlias {
                    foo: {
                        name
                    }
                };
            """)

    async def test_edgeql_migration_eq_40(self):
        # testing schema alias
        await self.migrate(r"""
            type Base;

            type Foo {
                property name -> str
            }

            type Bar {
                property name -> str
            }

            alias BaseAlias := (
                SELECT Base {
                    foo := (SELECT Foo FILTER .name = 'foo_40')
                }
            )
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base;
            INSERT Foo {name := 'foo_40'};
            INSERT Bar {name := 'bar_40'};
        """)

        await self.assert_query_result(
            r"""
                SELECT BaseAlias {
                    foo: {
                        name
                    }
                };
            """,
            [{
                'foo': [{
                    'name': 'foo_40'
                }]
            }]
        )

        await self.migrate(r"""
            type Base;

            type Foo {
                property name -> str
            }

            type Bar {
                property name -> str
            }

            alias BaseAlias := (
                SELECT Base {
                    # keep the name, but change the type
                    foo := (SELECT Bar FILTER .name = 'bar_40')
                }
            )
        """)

        await self.assert_query_result(
            r"""
                SELECT BaseAlias {
                    foo: {
                        name
                    }
                };
            """,
            [{
                'foo': [{
                    'name': 'bar_40'
                }]
            }]
        )

    async def test_edgeql_migration_eq_41(self):
        # testing schema alias
        await self.migrate(r"""
            type Base;

            type Foo {
                property name -> str
            }

            alias BaseAlias := (
                SELECT Base {
                    foo := (
                        SELECT Foo {
                            @bar := 'foo_bar_alias_41'
                        }
                        FILTER .name = 'base_alias_41'
                    )
                }
            )
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base;
            INSERT Foo {name := 'base_alias_41'};
        """)

        await self.assert_query_result(
            r"""
                SELECT BaseAlias {
                    foo: {
                        name,
                        @bar
                    }
                };
            """,
            [{
                'foo': [{
                    'name': 'base_alias_41',
                    '@bar': 'foo_bar_alias_41',
                }]
            }]
        )

        await self.migrate(r"""
            type Base;

            type Foo {
                property name -> str
            }

            alias BaseAlias := (
                SELECT Base {
                    foo := (
                        SELECT Foo {
                            # "rename" a computable link property, since
                            # the value is given and not stored, this is
                            # no different from dropping original and
                            # creating a new multi-link
                            @baz := 'foo_bar_alias_41'
                        }
                        FILTER .name = 'base_alias_41'
                    )
                }
            )
        """)

        await self.assert_query_result(
            r"""
                SELECT BaseAlias {
                    foo: {
                        name,
                        @baz
                    }
                };
            """,
            [{
                'foo': [{
                    'name': 'base_alias_41',
                    '@baz': 'foo_bar_alias_41'
                }]
            }]
        )

        with self.assertRaisesRegex(
                edgedb.InvalidReferenceError,
                r"link 'foo' .* has no property 'bar'"):
            await self.con.execute(r"""
                SELECT BaseAlias {
                    foo: {
                        name,
                        @bar
                    }
                };
            """)

    async def test_edgeql_migration_eq_42(self):
        # testing schema alias
        await self.migrate(r"""
            type Base;

            type Foo {
                property name -> str
            }

            alias BaseAlias := (
                SELECT Base {
                    foo := (
                        SELECT Foo {
                            @bar := 'foo_bar_alias_42'
                        }
                        FILTER .name = 'base_alias_42'
                    )
                }
            )
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base;
            INSERT Foo {name := 'base_alias_42'};
        """)

        await self.assert_query_result(
            r"""
                SELECT BaseAlias {
                    foo: {
                        name,
                        @bar
                    }
                };
            """,
            [{
                'foo': [{
                    'name': 'base_alias_42',
                    '@bar': 'foo_bar_alias_42',
                }]
            }]
        )

        await self.migrate(r"""
            type Base;

            type Foo {
                property name -> str
            }

            alias BaseAlias := (
                SELECT Base {
                    foo := (
                        SELECT Foo {
                            # keep the name, but change the type
                            @bar := 42
                        }
                        FILTER .name = 'base_alias_42'
                    )
                }
            )
        """)

        await self.assert_query_result(
            r"""
                SELECT BaseAlias {
                    foo: {
                        name,
                        @bar
                    }
                };
            """,
            [{
                'foo': [{
                    'name': 'base_alias_42',
                    '@bar': 42,
                }]
            }]
        )

    async def test_edgeql_migration_eq_43(self):
        await self.migrate(r"""
            abstract link Ordered {
                property index -> int32;
            }
            type User;
            abstract type Permissions {
                multi link owners extending Ordered -> User;
            };
        """)
        await self.migrate(r"")

    async def test_edgeql_migration_permissions_03a(self):
        # Check tracing dependency works
        await self.migrate(r"""
          function test(x: int64) -> int64 {
              using (x);
              required_permissions := foo;
          };
          permission foo;
       """)

    async def test_edgeql_migration_permissions_03b(self):
        # Check tracing dependency works
        await self.migrate(r"""
          function test(x: int64) -> int64 {
              using (1);
              required_permissions := {foo, bar};
          };
          permission foo;
          permission bar;
       """)

    async def test_edgeql_migration_permissions_03c(self):
        # Check tracing dependency works
        await self.migrate(r"""
          permission foo;
          permission bar;
          function test(x: int64) -> int64 {
              using (1);
              required_permissions := {foo, bar};
          };
       """)

    async def test_edgeql_migration_permissions_03d(self):
        # Sigh... test using POPULATE MIGRATION also...
        # Check tracing dependency works
        await tb.DDLTestCase.migrate(self, r"""
          permission foo;
          function test(x: int64) -> int64 {
              using (x);
              required_permissions := foo;
          };
       """)

    async def test_edgeql_migration_index_01(self):
        await self.migrate('''
            type Message {
                required property text -> str;
                index on (.text);
            };
        ''')

        await self.migrate('''
            type Message {
                required property text -> str;
                property ts -> datetime;
                index on (.text);
                index on (.ts);
            };
        ''')

        await self.assert_query_result(
            r"""
                SELECT count((SELECT schema::ObjectType
                              FILTER .name = 'test::Message').indexes)
            """,
            [2],
        )

    async def test_edgeql_migration_rebase_01(self):
        await self.migrate(r"""
            abstract type C;
            abstract type P {
                property p -> str;
                property p2 -> str;
                index on (.p);
            };
            type Foo extending C {
                property foo -> str;
            }
        """)

        await self.migrate(r"""
            abstract type C;
            abstract type P {
                property p -> str;
                property p2 -> str;
                index on (.p);
            };
            type Foo extending C, P {
                property foo -> str;
            }
        """)

    async def test_edgeql_migration_rebase_02(self):
        await self.migrate('''
            type User;
            abstract type Event {
              required property createdAt -> datetime {
                default := datetime_current();
              }

              required link user -> User;
            }

            type Post extending Event {
              required property content -> str {
                constraint min_len_value(1);
                constraint max_len_value(280);
              }
            }
        ''')

        await self.start_migration('''
            type User;
            abstract type Event {
              required property createdAt -> datetime {
                default := datetime_current();
              }

              required link user -> User;
            }

            abstract type HasContent {
              required property content -> str {
                constraint min_len_value(1);
                constraint max_len_value(280);
              }
            }

            type Post extending Event, HasContent {
            }

            type Reply extending Event, HasContent {
              required link post -> Post;
            }
        ''')

        # N.B.: these prompts are OK but not canonical; if they are
        # broken in favor of something better, just fix them.
        await self.interact([
            "did you create object type 'test::HasContent'?",
            "did you alter object type 'test::Post'?",
            "did you alter property 'content' of object type 'test::Post'?",
            "did you create object type 'test::Reply'?",
        ])

    async def test_edgeql_migration_rebase_03(self):
        await self.migrate('''
            abstract type Named {
                required property name -> str;
            };

            type Org;

            abstract type OrgBound {
                required link org -> Org;
            };

            abstract type OrgUniquelyNamed
                extending Named, OrgBound
            {
                constraint exclusive on ((.name, .org))
            }
        ''')

        await self.start_migration('''
            abstract type Named {
                required property name -> str;
            };

            type Org;
            abstract type Resource;

            abstract type OrgBound {
                required link org -> Org;
            };

            abstract type OrgUniquelyNamedResource
                extending Named, Resource, OrgBound
            {
                delegated constraint exclusive on ((.name, .org))
            }
        ''')

        # N.B.: these prompts are OK but not canonical; if they are
        # broken in favor of something better, just fix them.
        await self.interact([
            ("did you drop object type 'test::OrgUniquelyNamed'?", "n"),
            "did you create object type 'test::Resource'?",
            "did you rename object type 'test::OrgUniquelyNamed' to "
            "'test::OrgUniquelyNamedResource'?",

            "did you alter object type 'test::OrgUniquelyNamedResource'?",
        ])

    async def test_edgeql_migration_rename_01(self):
        await self.migrate('''
            type Foo;
        ''')

        await self.start_migration('''
            type Bar {
                property asdf -> str;
            };
        ''')

        await self.interact([
            "did you rename object type 'test::Foo' to 'test::Bar'?",
            "did you create property 'asdf' of object type 'test::Bar'?",
        ])

    async def test_edgeql_migration_rename_02(self):
        await self.migrate('''
            type Foo {
                property asdf -> str;
            };
            type Bar extending Foo {
                overloaded property asdf -> str;
            };
        ''')

        await self.start_migration('''
            type Foo {
                property womp -> str;
            };
            type Bar extending Foo {
                overloaded property womp -> str {
                    annotation title := "foo";
                };
            };
        ''')

        await self.interact([
            "did you rename property 'asdf' of object type 'test::Foo' to "
            "'womp'?",

            "did you create annotation 'std::title' of property 'womp'?",
        ])

    async def test_edgeql_migration_rename_03(self):
        await self.migrate('''
            abstract constraint Asdf { using (__subject__ < 10) };
            type Foo {
                property x -> int64 {
                    constraint Asdf;
                }
            }
            type Bar extending Foo;
        ''')

        await self.start_migration('''
            abstract constraint Womp { using (__subject__ < 10) };
            type Foo {
                property x -> int64 {
                    constraint Womp;
                }
            }
            type Bar extending Foo;
        ''')

        await self.interact([
            "did you rename abstract constraint 'test::Asdf' to "
            "'test::Womp'?",
        ])

    async def test_edgeql_migration_eq_function_01(self):
        await self.migrate(r"""
            function hello01(a: int64) -> str
                using edgeql $$
                    SELECT 'hello' ++ <str>a
                $$
        """)
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.assert_query_result(
            r"""SELECT hello01(1);""",
            ['hello1'],
        )

        # add an extra parameter with a default (so it can be omitted
        # in principle)
        await self.migrate(r"""
            function hello01(a: int64, b: int64=42) -> str
                using edgeql $$
                    SELECT 'hello' ++ <str>(a + b)
                $$
        """)

        await self.assert_query_result(
            r"""SELECT hello01(1);""",
            ['hello43'],
        )

        await self.assert_query_result(
            r"""SELECT hello01(1, 2);""",
            ['hello3'],
        )

    async def test_edgeql_migration_eq_function_02(self):
        await self.migrate(r"""
            function hello02(a: int64) -> str
                using edgeql $$
                    SELECT 'hello' ++ <str>a
                $$
        """)
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.assert_query_result(
            r"""SELECT hello02(1);""",
            ['hello1'],
        )

        # add an extra parameter with a default (so it can be omitted
        # in principle)
        await self.migrate(r"""
            function hello02(a: int64, b: OPTIONAL int64=42) -> str
                using edgeql $$
                    SELECT 'hello' ++ <str>(a + (b ?? -1))
                $$
        """)

        await self.assert_query_result(
            r"""SELECT hello02(1);""",
            ['hello43'],
        )

        await self.assert_query_result(
            r"""SELECT hello02(1, 2);""",
            ['hello3'],
        )

        await self.assert_query_result(
            r"""SELECT hello02(1, <int64>{});""",
            ['hello0'],
        )

    async def test_edgeql_migration_eq_function_03(self):
        await self.migrate(r"""
            function hello03(a: int64) -> str
                using edgeql $$
                    SELECT 'hello' ++ <str>a
                $$
        """)
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.assert_query_result(
            r"""SELECT hello03(1);""",
            ['hello1'],
        )

        # add an extra parameter with a default (so it can be omitted
        # in principle)
        await self.migrate(r"""
            function hello03(a: int64, NAMED ONLY b: int64=42) -> str
                using edgeql $$
                    SELECT 'hello' ++ <str>(a + b)
                $$
        """)

        await self.assert_query_result(
            r"""SELECT hello03(1);""",
            ['hello43'],
        )

        await self.assert_query_result(
            r"""SELECT hello03(1, b := 2);""",
            ['hello3'],
        )

    async def test_edgeql_migration_eq_function_04(self):
        await self.migrate(r"""
            function hello04(a: int64) -> str
                using edgeql $$
                    SELECT 'hello' ++ <str>a
                $$
        """)
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.assert_query_result(
            r"""SELECT hello04(1);""",
            ['hello1'],
        )

        # same parameters, different return type
        await self.migrate(r"""
            function hello04(a: int64) -> int64
                using edgeql $$
                    SELECT -a
                $$
        """)

        await self.assert_query_result(
            r"""SELECT hello04(1);""",
            [-1],
        )

    async def test_edgeql_migration_eq_function_05(self):
        await self.migrate(r"""
            function hello05(a: int64) -> str
                using edgeql $$
                    SELECT <str>a
                $$
        """)
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.assert_query_result(
            r"""SELECT hello05(1);""",
            ['1'],
        )

        # same parameters, different return type (array)
        await self.migrate(r"""
            function hello05(a: int64) -> array<int64>
                using edgeql $$
                    SELECT [a]
                $$
        """)

        await self.assert_query_result(
            r"""SELECT hello05(1);""",
            [[1]],
        )

    @test.xfail('''
        It should be possible to change the underlying function (to a
        compatible one) of a default value without explicitly dropping
        the default first.

        Currently this kind of works... by proposing we delete the property
        and recreate it.
    ''')
    async def test_edgeql_migration_eq_function_06(self):
        await self.migrate(r"""
            function hello06(a: int64) -> str
                using edgeql $$
                    SELECT <str>a
                $$;

            type Base {
                property foo -> int64 {
                    # use the function in default value computation
                    default := len(hello06(2) ++ hello06(123))
                }
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base;
        """)

        await self.assert_query_result(
            r"""SELECT Base.foo;""",
            {4},
        )

        # same parameters, different return type (array)
        await self.migrate(r"""
            function hello06(a: int64) -> array<int64>
                using edgeql $$
                    SELECT [a]
                $$;

            type Base {
                property foo -> int64 {
                    # use the function in default value computation
                    default := len(hello06(2) ++ hello06(123))
                }
            }
        """)
        await self.con.execute(r"""
            INSERT Base;
        """)

        await self.assert_query_result(
            r"""SELECT Base.foo;""",
            {4, 2},
        )

    async def test_edgeql_migration_eq_function_07(self):
        await self.migrate(r"""
            function hello07(a: int64) -> str
                using edgeql $$
                    SELECT <str>a
                $$;

            type Base {
                # use the function in computable value
                property foo := len(hello07(2) ++ hello07(123))
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base;
        """)

        await self.assert_query_result(
            r"""SELECT Base.foo;""",
            {4},
        )

        # same parameters, different return type (array)
        await self.migrate(r"""
            function hello07(a: int64) -> array<int64>
                using edgeql $$
                    SELECT [a]
                $$;

            type Base {
                # use the function in computable value
                property foo := len(hello07(2) ++ hello07(123))
            }
        """)

        await self.assert_query_result(
            r"""SELECT Base.foo;""",
            {2},
        )

    async def test_edgeql_migration_eq_function_08(self):
        await self.migrate(r"""
            function hello08(a: int64) -> str
                using edgeql $$
                    SELECT <str>a
                $$;

            # use the function in a alias directly
            alias foo := len(hello08(2) ++ hello08(123));
        """)
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.assert_query_result(
            r"""SELECT foo;""",
            {4},
        )

        # same parameters, different return type (array)
        await self.migrate(r"""
            function hello08(a: int64) -> array<int64>
                using edgeql $$
                    SELECT [a]
                $$;

            # use the function in a alias directly
            alias foo := len(hello08(2) ++ hello08(123));
        """)

        await self.assert_query_result(
            r"""SELECT foo;""",
            {2},
        )

    async def test_edgeql_migration_eq_function_09(self):
        await self.migrate(r"""
            function hello09(a: int64) -> str
                using edgeql $$
                    SELECT <str>a
                $$;

            type Base;

            # use the function in a alias directly
            alias BaseAlias := (
                SELECT Base {
                    foo := len(hello09(2) ++ hello09(123))
                }
            );
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base;
        """)

        await self.assert_query_result(
            r"""SELECT BaseAlias.foo;""",
            {4},
        )

        # same parameters, different return type (array)
        await self.migrate(r"""
            function hello09(a: int64) -> array<int64>
                using edgeql $$
                    SELECT [a]
                $$;

            type Base;

            # use the function in a alias directly
            alias BaseAlias := (
                SELECT Base {
                    foo := len(hello09(2) ++ hello09(123))
                }
            );
        """)

        await self.assert_query_result(
            r"""SELECT BaseAlias.foo;""",
            {2},
        )

    async def test_edgeql_migration_eq_function_10(self):
        await self.migrate(r"""
            function hello10(a: int64) -> str
                using edgeql $$
                    SELECT <str>a
                $$;

            type Base {
                required property foo -> int64 {
                    # use the function in a constraint expression
                    constraint expression on (len(hello10(__subject__)) < 2)
                }
            }
        """)
        await self.con.execute(r"""
            SET MODULE test;
        """)

        with self.assertRaisesRegex(
                edgedb.ConstraintViolationError,
                r'invalid foo'):
            async with self.con.transaction():
                await self.con.execute(r"""
                    INSERT Base {foo := 42};
                """)

        # same parameters, different return type (array)
        await self.migrate(r"""
            function hello10(a: int64) -> array<int64>
                using edgeql $$
                    SELECT [a]
                $$;

            type Base {
                required property foo -> int64 {
                    # use the function in a constraint expression
                    constraint expression on (len(hello10(__subject__)) < 2)
                }
            }
        """)

        # no problem with the constraint now
        await self.con.execute(r"""
            INSERT Base {foo := 42};
        """)

    async def test_edgeql_migration_eq_function_11(self):
        await self.migrate(r"""
            function hello11(a: int64) -> str
                using edgeql $$
                    SELECT 'hello' ++ <str>a
                $$
        """)
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.assert_query_result(
            r"""SELECT hello11(1);""",
            ['hello1'],
        )

        await self.migrate(r"""
            # replace the function with a new one by the same name
            function hello11(a: str) -> str
                using edgeql $$
                    SELECT 'hello' ++ a
                $$
        """)

        await self.assert_query_result(
            r"""SELECT hello11(' world');""",
            ['hello world'],
        )

        # make sure that the old one is gone
        with self.assertRaisesRegex(
                edgedb.QueryError,
                r'function "hello11\(arg0: std::int64\)" does not exist'):
            await self.con.execute(
                r"""SELECT hello11(1);"""
            )

    async def test_edgeql_migration_eq_function_12(self):
        await self.migrate(r"""
            function hello12(a: int64) -> str
                using edgeql $$
                    SELECT 'hello' ++ <str>a
                $$;
        """)
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.assert_query_result(
            r"""SELECT hello12(1);""",
            ['hello1'],
        )

        await self.migrate(r"""
            function hello12(a: int64) -> str
                using edgeql $$
                    SELECT 'hello' ++ <str>a
                $$;

            # make the function polymorphic
            function hello12(a: str) -> str
                using edgeql $$
                    SELECT 'hello' ++ a
                $$;
        """)

        await self.assert_query_result(
            r"""SELECT hello12(' world');""",
            ['hello world'],
        )

        # make sure that the old one still works
        await self.assert_query_result(
            r"""SELECT hello12(1);""",
            ['hello1'],
        )

    async def test_edgeql_migration_eq_function_13(self):
        # this is the inverse of test_edgeql_migration_eq_function_12
        await self.migrate(r"""
            # start with a polymorphic function
            function hello13(a: int64) -> str
                using edgeql $$
                    SELECT 'hello' ++ <str>a
                $$;

            function hello13(a: str) -> str
                using edgeql $$
                    SELECT 'hello' ++ a
                $$;
        """)
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.assert_query_result(
            r"""SELECT hello13(' world');""",
            ['hello world'],
        )
        await self.assert_query_result(
            r"""SELECT hello13(1);""",
            ['hello1'],
        )

        await self.migrate(r"""
            # remove one of the 2 versions
            function hello13(a: int64) -> str
                using edgeql $$
                    SELECT 'hello' ++ <str>a
                $$;
        """)

        await self.assert_query_result(
            r"""SELECT hello13(1);""",
            ['hello1'],
        )

        # make sure that the other one is gone
        with self.assertRaisesRegex(
                edgedb.QueryError,
                r'function "hello13\(arg0: std::str\)" does not exist'):
            await self.con.execute(
                r"""SELECT hello13(' world');"""
            )

    async def test_edgeql_migration_eq_function_14(self):
        await self.migrate(r"""
            function hello14(a: str, b: str) -> str
                using edgeql $$
                    SELECT a ++ b
                $$
        """)
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.assert_query_result(
            r"""SELECT hello14('hello', '14');""",
            ['hello14'],
        )

        await self.migrate(r"""
            # Replace the function with a new one by the same name,
            # but working with arrays.
            function hello14(a: array<str>, b: array<str>) -> array<str>
                using edgeql $$
                    SELECT a ++ b
                $$
        """)

        await self.assert_query_result(
            r"""SELECT hello14(['hello'], ['14']);""",
            [['hello', '14']],
        )

        # make sure that the old one is gone
        with self.assertRaisesRegex(
                edgedb.QueryError,
                r'function "hello14\(arg0: std::str, arg1: std::str\)" '
                r'does not exist'):
            await self.assert_query_result(
                r"""SELECT hello14('hello', '14');""",
                ['hello14'],
            )

    async def test_edgeql_migration_eq_function_15(self):
        await self.migrate(r"""
            function hello15(a: str, b: str) -> str
                using edgeql $$
                    SELECT a ++ b
                $$
        """)
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.assert_query_result(
            r"""SELECT hello15('hello', '15');""",
            ['hello15'],
        )

        await self.migrate(r"""
            # Replace the function with a new one by the same name,
            # but working with arrays.
            function hello15(a: tuple<str, str>) -> str
                using edgeql $$
                    SELECT a.0 ++ a.1
                $$
        """)

        await self.assert_query_result(
            r"""SELECT hello15(('hello', '15'));""",
            ['hello15'],
        )

        # make sure that the old one is gone
        with self.assertRaisesRegex(
                edgedb.QueryError,
                r'function "hello15\(arg0: std::str, arg1: std::str\)" '
                r'does not exist'):
            await self.assert_query_result(
                r"""SELECT hello15('hello', '15');""",
                ['hello15'],
            )

    async def test_edgeql_migration_eq_function_16(self):
        # Test prop default and function order of definition. The
        # function happens to be shadowing a "std" function. We expect
        # that the function `test::to_upper` will actually be used.
        #
        # See also `test_schema_get_migration_21`
        await self.migrate(r"""
            type Foo16 {
                property name -> str {
                    default := str_upper('some_name');
                };
            }

            function str_upper(val: str) -> str {
                using (SELECT '^^' ++ std::str_upper(val) ++ '^^');
            }
        """)
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.assert_query_result(
            r"""SELECT str_upper('hello');""",
            ['^^HELLO^^'],
        )

        await self.con.execute("""
            INSERT Foo16;
        """)
        await self.assert_query_result(
            r"""SELECT Foo16.name;""",
            ['^^SOME_NAME^^'],
        )

    async def test_edgeql_migration_enum_and_var_function_01(self):
        # Create an enum and a variadic function that references it
        # in the same migration. Issue #4213.
        await self.migrate(r"""
            scalar type E extending enum<a, b, c>;
            function f(variadic e: E) -> bool using (true);
        """)

    async def test_edgeql_migration_eq_linkprops_01(self):
        await self.migrate(r"""
            type Child;

            type Base {
                link foo -> Child;
            };
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {
                foo := (INSERT Child)
            };
        """)

        # Migration adding a link property.
        await self.migrate(r"""
            type Child;

            type Base {
                link foo -> Child {
                    property bar -> str
                }
            };
        """)

        # actually record a link property
        await self.con.execute(r"""
            UPDATE
                Base
            SET {
                foo := .foo {
                    @bar := 'lp01'
                }
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo: { @bar }
                };
            """,
            [{'foo': {'@bar': 'lp01'}}],
        )

    async def test_edgeql_migration_eq_linkprops_02(self):
        await self.migrate(r"""
            type Child;

            type Base {
                link foo -> Child {
                    property bar -> str
                }
            };
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {foo := (INSERT Child)};
            UPDATE Base
            SET {
                foo := .foo { @bar := 'lp02' },
            };
        """)

        await self.migrate(r"""
            type Child;

            type Base {
                link foo -> Child {
                    # change the link property name
                    property bar2 -> str
                }
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo: { @bar2 }
                };
            """,
            [{'foo': {'@bar2': 'lp02'}}],
        )

    async def test_edgeql_migration_eq_linkprops_03(self):
        await self.migrate(r"""
            type Child;

            type Base {
                link foo -> Child {
                    property bar -> int64
                }
            };
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {foo := (INSERT Child)};
            UPDATE Base
            SET {
                foo := .foo { @bar := 3 },
            };
        """)

        await self.migrate(r"""
            type Child;

            type Base {
                link foo -> Child {
                    # change the link property type
                    property bar -> int32
                }
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo: { @bar }
                };
            """,
            [{'foo': {'@bar': 3}}],
        )

    async def test_edgeql_migration_eq_linkprops_04(self):
        await self.migrate(r"""
            type Child;

            type Base {
                link foo -> Child {
                    property bar -> str
                }
            };
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {foo := (INSERT Child)};
            UPDATE Base
            SET {
                foo := .foo { @bar := 'lp04' },
            };
        """)

        await self.migrate(r"""
            type Child;

            type Base {
                # change the link cardinality
                multi link foo -> Child {
                    property bar -> str
                }
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo: { @bar }
                };
            """,
            [{'foo': [{'@bar': 'lp04'}]}],
        )

    async def test_edgeql_migration_eq_linkprops_05(self):
        await self.migrate(r"""
            type Child;

            type Base {
                multi link foo -> Child {
                    property bar -> str
                }
            };
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {foo := (INSERT Child)};
            UPDATE Base
            SET {
                foo := .foo { @bar := 'lp05' },
            };
        """)

        await self.migrate(r"""
            type Child;

            type Base {
                # change the link cardinality
                link foo -> Child {
                    property bar -> str
                }
            };
        """, user_input=[
            'SELECT .foo LIMIT 1'
        ])

        await self.assert_query_result(
            r"""
                SELECT Base {
                    foo: { @bar }
                };
            """,
            [{'foo': {'@bar': 'lp05'}}],
        )

    async def test_edgeql_migration_eq_linkprops_06(self):
        await self.migrate(r"""
            type Child;

            type Base {
                link child -> Child {
                    property foo -> str;
                }
            };
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {child := (INSERT Child)};
            UPDATE Base
            SET {
                child := .child {
                    @foo := 'lp06',
                },
            };
        """)

        await self.migrate(r"""
            type Child;

            type Base {
                link child -> Child {
                    property foo -> str;
                    # add another link prop
                    property bar -> int64;
                }
            };
        """)
        # update the existing data with a new link prop 'bar'
        await self.con.execute(r"""
            UPDATE Base
            SET {
                child := .child {
                    @bar := 111,
                    @foo := 'lp06',
                },
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    child: {
                        @foo,
                        @bar
                    }
                };
            """,
            [{
                'child': {
                    '@foo': 'lp06',
                    '@bar': 111
                }
            }],
        )

    async def test_edgeql_migration_eq_linkprops_07(self):
        await self.migrate(r"""
            type Child;

            type Base {
                link child -> Child
            };

            type Derived extending Base {
                overloaded link child -> Child {
                    property foo -> str
                }
            };
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Derived {child := (INSERT Child)};
            UPDATE Derived
            SET {
                child := .child {
                    @foo := 'lp07',
                },
            };
        """)

        await self.migrate(r"""
            type Child;

            type Base {
                # move the link property earlier in the inheritance tree
                link child -> Child {
                    property foo -> str
                }
            };

            type Derived extending Base;
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    child: {
                        @foo,
                    }
                };
            """,
            [{
                'child': {
                    '@foo': 'lp07',
                }
            }],
        )

    async def test_edgeql_migration_eq_linkprops_08(self):
        await self.migrate(r"""
            type Child;

            type Base {
                link child -> Child {
                    property foo -> str
                }
            };

            type Derived extending Base;
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Derived {child := (INSERT Child)};
        """)
        await self.con.execute(r"""
            UPDATE Derived
            SET {
                child := .child {
                    @foo := 'lp08',
                },
            };
        """)

        await self.migrate(r"""
            type Child;

            type Base {
                link child -> Child
            };

            type Derived extending Base {
                overloaded link child -> Child {
                    # move the link property later in the inheritance tree
                    property foo -> str
                }
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Derived {
                    children := count(.child)
                };
            """,
            [{
                'children': 1,
            }],
        )

        await self.assert_query_result(
            r"""
                SELECT Derived {
                    child: {
                        @foo,
                    }
                };
            """,
            [{
                'child': {
                    '@foo': 'lp08',
                }
            }],
        )

    async def test_edgeql_migration_eq_linkprops_09(self):
        await self.migrate(r"""
            type Child;

            type Base {
                link child -> Child
            };

            type Derived extending Base {
                overloaded link child -> Child {
                    property foo -> str
                }
            };
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Derived {child := (INSERT Child)};
            UPDATE Derived
            SET {
                child := .child {
                    @foo := 'lp09',
                },
            };
        """)

        await self.migrate(r"""
            type Child;

            # factor out link property all the way to an abstract link
            abstract link base_child {
                property foo -> str;
            }

            type Base {
                link child extending base_child -> Child;
            };

            type Derived extending Base;
        """)

        await self.assert_query_result(
            r"""
                SELECT Base {
                    child: {
                        @foo,
                    }
                };
            """,
            [{
                'child': {
                    '@foo': 'lp09',
                }
            }],
        )

    async def test_edgeql_migration_eq_linkprops_10(self):
        # reverse of the test_edgeql_migration_eq_linkprops_09 refactoring
        await self.migrate(r"""
            type Child;

            abstract link base_child {
                property foo -> str;
            }

            type Base {
                link child extending base_child -> Child;
            };

            type Derived extending Base;
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Derived {child := (INSERT Child)};
            UPDATE Derived
            SET {
                child := .child {
                    @foo := 'lp10',
                },
            };
        """)

        await self.migrate(r"""
            type Child;

            type Base {
                link child -> Child
            };

            type Derived extending Base {
                overloaded link child -> Child {
                    # move the link property later in the inheritance tree
                    property foo -> str
                }
            };
        """)

        await self.assert_query_result(
            r"""
                SELECT Derived {
                    child: {
                        @foo,
                    }
                };
            """,
            [{
                'child': {
                    '@foo': 'lp10',
                }
            }],
        )

        await self.migrate("")

    async def test_edgeql_migration_eq_linkprops_11(self):
        # merging a link with the same properties
        await self.migrate(r"""
            type Thing;

            type Owner {
                link item -> Thing {
                    property foo -> str;
                }
            };

            type Renter {
                link item -> Thing {
                    property foo -> str;
                }
            };
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Owner {item := (INSERT Thing)};
            UPDATE Owner
            SET {
                item := .item {
                    @foo := 'owner_lp11',
                },
            };

            INSERT Renter {item := (INSERT Thing)};
            UPDATE Renter
            SET {
                item := .item {
                    @foo := 'renter_lp11',
                },
            };
        """)

        await self.migrate(r"""
            type Thing;

            type Base {
                link item -> Thing {
                    property foo -> str;
                }
            };

            type Owner extending Base;

            type Renter extending Base;
        """)

        await self.assert_query_result(
            r"""
                SELECT Owner {
                    item: {
                        @foo,
                    }
                };
            """,
            [{
                'item': {
                    '@foo': 'owner_lp11',
                }
            }],
        )

        await self.assert_query_result(
            r"""
                SELECT Renter {
                    item: {
                        @foo,
                    }
                };
            """,
            [{
                'item': {
                    '@foo': 'renter_lp11',
                }
            }],
        )

    async def test_edgeql_migration_eq_linkprops_12(self):
        # merging a link with different properties
        await self.migrate(r"""
            type Thing;

            type Owner {
                link item -> Thing {
                    property foo -> str;
                }
            };

            type Renter {
                link item -> Thing {
                    property bar -> str;
                }
            };
        """)
        await self.con.execute(r"""
            SET MODULE test;

            INSERT Owner {item := (INSERT Thing)};
            UPDATE Owner
            SET {
                item := .item {
                    @foo := 'owner_lp11',
                },
            };

            INSERT Renter {item := (INSERT Thing)};
            UPDATE Renter
            SET {
                item := .item {
                    @bar := 'renter_lp11',
                },
            };
        """)

        await self.migrate(r"""
            type Thing;

            type Base {
                link item -> Thing {
                    property foo -> str;
                    property bar -> str;
                }
            };

            type Owner extending Base;

            type Renter extending Base;
        """)

        await self.assert_query_result(
            r"""
                SELECT Owner {
                    item: {
                        @foo,
                        @bar,
                    }
                };
            """,
            [{
                'item': {
                    '@foo': 'owner_lp11',
                    '@bar': None,
                }
            }],
        )

        await self.assert_query_result(
            r"""
                SELECT Renter {
                    item: {
                        @foo,
                        @bar,
                    }
                };
            """,
            [{
                'item': {
                    '@foo': None,
                    '@bar': 'renter_lp11',
                }
            }],
        )

    async def test_edgeql_migration_eq_annotation_01(self):
        await self.migrate(r"""
            type Base;
        """)
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    annotations: {
                        name,
                        @value
                    } ORDER BY .name
                }
                FILTER .name LIKE 'test::%'
                ORDER BY .name;
            """,
            [{
                'name': 'test::Base',
                'annotations': [],
            }],
        )

        await self.migrate(r"""
            type Base {
                # add a title annotation
                annotation title := 'Base description 01'
            }
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    annotations: {
                        name,
                        @value
                    } ORDER BY .name
                }
                FILTER .name LIKE 'test::%'
                ORDER BY .name;
            """,
            [{
                'name': 'test::Base',
                'annotations': [{
                    'name': 'std::title',
                    '@value': 'Base description 01'
                }],
            }],
        )

        await self.migrate(r"""
            # add inheritable and non-inheritable annotations
            abstract annotation foo_anno;
            abstract inheritable annotation bar_anno;

            type Base {
                annotation title := 'Base description 01';
                annotation foo_anno := 'Base foo_anno 01';
                annotation bar_anno := 'Base bar_anno 01';
            }
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    annotations: {
                        name,
                        @value
                    } ORDER BY .name
                }
                FILTER .name LIKE 'test::%'
                ORDER BY .name;
            """,
            [{
                'name': 'test::Base',
                'annotations': [{
                    'name': 'std::title',
                    '@value': 'Base description 01'
                }, {
                    'name': 'test::bar_anno',
                    '@value': 'Base bar_anno 01'
                }, {
                    'name': 'test::foo_anno',
                    '@value': 'Base foo_anno 01'
                }],
            }],
        )

        await self.migrate(r"""
            abstract annotation foo_anno;
            abstract inheritable annotation bar_anno;

            type Base {
                annotation title := 'Base description 01';
                annotation foo_anno := 'Base foo_anno 01';
                annotation bar_anno := 'Base bar_anno 01';
            }

            # extend Base
            type Derived extending Base;
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    annotations: {
                        name,
                        @value
                    } ORDER BY .name
                }
                FILTER .name LIKE 'test::%'
                ORDER BY .name;
            """,
            [{
                'name': 'test::Base',
                'annotations': [{
                    'name': 'std::title',
                    '@value': 'Base description 01'
                }, {
                    'name': 'test::bar_anno',
                    '@value': 'Base bar_anno 01'
                }, {
                    'name': 'test::foo_anno',
                    '@value': 'Base foo_anno 01'
                }],
            }, {
                'name': 'test::Derived',
                'annotations': [{
                    'name': 'test::bar_anno',
                    '@value': 'Base bar_anno 01'
                }],
            }],
        )

    async def test_edgeql_migration_eq_annotation_02(self):
        await self.migrate(r"""
            type Base;
        """)
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    annotations: {
                        name,
                        @value
                    } ORDER BY .name
                }
                FILTER .name LIKE 'test::%'
                ORDER BY .name;
            """,
            [{
                'name': 'test::Base',
                'annotations': [],
            }],
        )

        await self.migrate(r"""
            abstract annotation foo_anno;

            type Base {
                annotation title := 'Base description 02';
                annotation foo_anno := 'Base foo_anno 02';
            }

            type Derived extending Base;
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    annotations: {
                        name,
                        @value
                    } ORDER BY .name
                }
                FILTER .name LIKE 'test::%'
                ORDER BY .name;
            """,
            [{
                'name': 'test::Base',
                'annotations': [{
                    'name': 'std::title',
                    '@value': 'Base description 02'
                }, {
                    'name': 'test::foo_anno',
                    '@value': 'Base foo_anno 02'
                }],
            }, {
                'name': 'test::Derived',
                # annotation not inherited
                'annotations': [],
            }],
        )

        await self.migrate(r"""
            # remove foo_anno
            type Base {
                annotation title := 'Base description 02';
            }

            type Derived extending Base;
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    annotations: {
                        name,
                        @value
                    } ORDER BY .name
                }
                FILTER .name LIKE 'test::%'
                ORDER BY .name;
            """,
            [{
                'name': 'test::Base',
                'annotations': [{
                    'name': 'std::title',
                    '@value': 'Base description 02'
                }],
            }, {
                'name': 'test::Derived',
                'annotations': [],
            }],
        )

    async def test_edgeql_migration_eq_annotation_03(self):
        await self.migrate(r"""
            type Base;
        """)
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    annotations: {
                        name,
                        @value
                    } ORDER BY .name
                }
                FILTER .name LIKE 'test::%'
                ORDER BY .name;
            """,
            [{
                'name': 'test::Base',
                'annotations': [],
            }],
        )

        await self.migrate(r"""
            abstract inheritable annotation bar_anno;

            type Base {
                annotation title := 'Base description 03';
                annotation bar_anno := 'Base bar_anno 03';
            }

            type Derived extending Base;
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    annotations: {
                        name,
                        @value
                    } ORDER BY .name
                }
                FILTER .name LIKE 'test::%'
                ORDER BY .name;
            """,
            [{
                'name': 'test::Base',
                'annotations': [{
                    'name': 'std::title',
                    '@value': 'Base description 03'
                }, {
                    'name': 'test::bar_anno',
                    '@value': 'Base bar_anno 03'
                }],
            }, {
                'name': 'test::Derived',
                # annotation inherited
                'annotations': [{
                    'name': 'test::bar_anno',
                    '@value': 'Base bar_anno 03'
                }],
            }],
        )

        await self.migrate(r"""
            # remove bar_anno
            type Base {
                annotation title := 'Base description 03';
            }

            type Derived extending Base;
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    annotations: {
                        name,
                        @value
                    } ORDER BY .name
                }
                FILTER .name LIKE 'test::%'
                ORDER BY .name;
            """,
            [{
                'name': 'test::Base',
                'annotations': [{
                    'name': 'std::title',
                    '@value': 'Base description 03'
                }],
            }, {
                'name': 'test::Derived',
                'annotations': [],
            }],
        )

    async def test_edgeql_migration_eq_annotation_04(self):
        # Test migration of annotation value and nothing else.
        await self.migrate(r"""
            abstract annotation description;

            type Base {
                annotation description := "1";
            }
        """)
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    annotations: {
                        name,
                        @value
                    } ORDER BY .name
                }
                FILTER .name LIKE 'test::%'
                ORDER BY .name;
            """,
            [{
                'name': 'test::Base',
                'annotations': [{
                    'name': 'test::description',
                    '@value': '1',
                }],
            }],
        )

        await self.migrate(r"""
            abstract annotation description;

            type Base {
                annotation description := "2";
            }
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    annotations: {
                        name,
                        @value
                    } ORDER BY .name
                }
                FILTER .name LIKE 'test::%'
                ORDER BY .name;
            """,
            [{
                'name': 'test::Base',
                'annotations': [{
                    'name': 'test::description',
                    '@value': '2',
                }],
            }],
        )

    async def test_edgeql_migration_describe_annot_01(self):
        await self.migrate('''
            abstract annotation foo;

            type Base {
                annotation foo := "1";
            };
        ''')

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    abstract annotation bar;

                    type Base {
                        annotation bar := "1";
                    };
                }
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text': (
                        'ALTER ABSTRACT ANNOTATION test::foo '
                        'RENAME TO test::bar;'
                    )
                }],
            },
        })

    async def test_edgeql_migration_eq_index_01(self):
        await self.con.execute("""
            SET MODULE test;
        """)
        await self.migrate(r"""
            type Base {
                property name -> str;
            }
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    indexes: {
                        expr
                    }
                }
                FILTER .name = 'test::Base';
            """,
            [{
                'name': 'test::Base',
                'indexes': [],
            }],
        )

        await self.migrate(r"""
            type Base {
                property name -> str;
                # an index
                index on (.name);
            }
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    indexes: {
                        expr
                    }
                }
                FILTER .name = 'test::Base';
            """,
            [{
                'name': 'test::Base',
                'indexes': [{
                    'expr': '.name'
                }]
            }],
        )

        await self.migrate(r"""
            type Base {
                # rename the indexed property
                property title -> str;
                index on (.title);
            }
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    indexes: {
                        expr
                    }
                }
                FILTER .name = 'test::Base';
            """,
            [{
                'name': 'test::Base',
                'indexes': [{
                    'expr': '.title'
                }]
            }],
        )

    async def test_edgeql_migration_eq_index_02(self):
        await self.migrate(r"""
            type Base {
                property name -> str;
                index on (.name);
            }
        """)
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    indexes: {
                        expr
                    }
                }
                FILTER .name = 'test::Base';
            """,
            [{
                'name': 'test::Base',
                'indexes': [{
                    'expr': '.name'
                }]
            }],
        )

        await self.migrate(r"""
            type Base {
                property name -> str;
                # remove the index
            }
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    indexes: {
                        expr
                    }
                }
                FILTER .name = 'test::Base';
            """,
            [{
                'name': 'test::Base',
                'indexes': [],
            }],
        )

    async def test_edgeql_migration_eq_index_03(self):
        await self.migrate(r"""
            type Base {
                property name -> int64;
            }
        """)
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    indexes: {
                        expr
                    }
                }
                FILTER .name = 'test::Base';
            """,
            [{
                'name': 'test::Base',
                'indexes': [],
            }],
        )

        await self.migrate(r"""
            type Base {
                property name -> int64;
                # an index
                index on (.name);
            }
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    indexes: {
                        expr
                    }
                }
                FILTER .name = 'test::Base';
            """,
            [{
                'name': 'test::Base',
                'indexes': [{
                    'expr': '.name'
                }]
            }],
        )

        await self.migrate(r"""
            type Base {
                # change the indexed property type
                property name -> int32;
                index on (.name);
            }
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    indexes: {
                        expr
                    }
                }
                FILTER .name = 'test::Base';
            """,
            [{
                'name': 'test::Base',
                'indexes': [{
                    'expr': '.name'
                }]
            }],
        )

    async def test_edgeql_migration_eq_index_04(self):
        await self.migrate(r"""
            type Base {
                property first_name -> str;
                property last_name -> str;
                property name := .first_name ++ ' ' ++ .last_name;
            }
        """)
        await self.con.execute("""
            SET MODULE test;
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    indexes: {
                        expr
                    }
                }
                FILTER .name = 'test::Base';
            """,
            [{
                'name': 'test::Base',
                'indexes': [],
            }],
        )

        await self.migrate(r"""
            type Base {
                property first_name -> str;
                property last_name -> str;
                property name := .first_name ++ ' ' ++ .last_name;
                # an index on a computable
                index on (.name);
            }
        """)

        await self.assert_query_result(
            r"""
                WITH MODULE schema
                SELECT ObjectType {
                    name,
                    indexes: {
                        expr
                    }
                }
                FILTER .name = 'test::Base';
            """,
            [{
                'name': 'test::Base',
                'indexes': [{
                    'expr': '.name'
                }]
            }],
        )

    async def test_edgeql_migration_eq_index_05(self):
        await self.migrate('''
            abstract type Named {
                index fts::index on (
                    std::fts::with_options(
                        .name, language := std::fts::Language.eng));
                required property name: std::str;
            };
        ''')

        await self.start_migration('''
            abstract type Named {
                index std::fts::index on (
                    std::fts::with_options(
                        .name, language := std::fts::Language.eng));
                required property name: std::str;
            };
        ''')

        # no changes
        await self.assert_describe_migration({
            'confirmed': [],
            'complete': True,
        })

    async def test_edgeql_migration_eq_collections_01(self):
        await self.migrate(r"""
            type Base;
        """)

        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base;
        """)

        await self.migrate(r"""
            type Base {
                property foo -> array<float32>;
            }
        """)

        await self.con.execute(r"""
            UPDATE Base
            SET {
                foo := [1.2, 4.5]
            };
        """)
        await self.assert_query_result(
            r"""SELECT Base.foo;""",
            [[1.2, 4.5]],
        )

    async def test_edgeql_migration_eq_collections_02(self):
        await self.migrate(r"""
            type Base;
        """)

        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base;
        """)

        await self.migrate(r"""
            type Base {
                property foo -> tuple<str, int32>;
            }
        """)

        await self.con.execute(r"""
            UPDATE Base
            SET {
                foo := ('hello', 42)
            };
        """)
        await self.assert_query_result(
            r"""SELECT Base.foo;""",
            [['hello', 42]],
        )

    async def test_edgeql_migration_eq_collections_03(self):
        await self.migrate(r"""
            type Base;
        """)

        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base;
        """)

        await self.migrate(r"""
            type Base {
                # nested collection
                property foo -> tuple<str, int32, array<float32>>;
            }
        """)

        await self.con.execute(r"""
            UPDATE Base
            SET {
                foo := ('test', 42, [1.2, 4.5])
            };
        """)
        await self.assert_query_result(
            r"""SELECT Base.foo;""",
            [['test', 42, [1.2, 4.5]]],
        )

    async def test_edgeql_migration_eq_collections_04(self):
        await self.migrate(r"""
            type Base;
        """)

        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base;
        """)

        await self.migrate(r"""
            type Base {
                property foo -> tuple<a: str, b: int32>;
            }
        """)

        await self.con.execute(r"""
            UPDATE Base
            SET {
                foo := (a := 'hello', b := 42)
            };
        """)
        await self.assert_query_result(
            r"""SELECT Base.foo;""",
            [{'a': 'hello', 'b': 42}],
        )

    async def test_edgeql_migration_eq_collections_06(self):
        await self.migrate(r"""
            type Base {
                property foo -> array<int32>;
            }
        """)

        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {
                foo := [1, 2]
            }
        """)
        # sanity check
        await self.assert_query_result(
            r"""SELECT Base.foo;""",
            [[1, 2]],
        )

        await self.migrate(r"""
            type Base {
                property foo -> array<float64>;
            }
        """)

        await self.assert_query_result(
            r"""SELECT Base.foo;""",
            [[1.0, 2.0]],
        )

    async def test_edgeql_migration_eq_collections_07(self):
        await self.con.execute("""
            SET MODULE test;
        """)
        await self.migrate(r"""
            type Base {
                # convert property type to tuple
                property bar -> array<str>;
                property foo -> tuple<str, int32>;
            }
        """)

        await self.con.execute(r"""
            INSERT Base {
                bar := ['123'],
                foo := ('test', <int32>7),
            }
        """)

        await self.migrate(
            r"""
                type Base {
                    property bar -> array<int64>;
                    property foo -> tuple<str, int32, int32>;
                }
            """,
            user_input=[
                "<array<int64>>.bar",
                "(.foo.0, .foo.1, 0)",
            ]
        )

        await self.assert_query_result(
            r"""SELECT Base.bar;""",
            [[123]],
        )

        await self.assert_query_result(
            r"""SELECT Base.foo;""",
            [['test', 7, 0]],
        )

    async def test_edgeql_migration_eq_collections_08(self):
        await self.migrate(r"""
            type Base {
                property foo -> tuple<int32, int32>;
            }
        """)

        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {
                foo := (0, 8)
            }
        """)

        await self.migrate(r"""
            type Base {
                # convert property type to a tuple with different (but
                # cast-compatible) element types
                property foo -> tuple<int64, int32>;
            }
        """)

        await self.assert_query_result(
            r"""SELECT Base.foo;""",
            [[0, 8]],
        )

    async def test_edgeql_migration_eq_collections_09(self):
        await self.migrate(r"""
            type Base {
                property foo -> tuple<str, int32>;
            }
        """)

        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {
                foo := ('test', 9)
            }
        """)

        await self.migrate(r"""
            type Base {
                # convert property type from unnamed to named tuple
                property foo -> tuple<a: str, b: int32>;
            }
        """)

        # In theory, since under normal circumstances we can cast an
        # unnamed tuple into named, it's reasonable to expect this
        # migration to preserve data here.
        await self.assert_query_result(
            r"""SELECT Base.foo;""",
            [{'a': 'test', 'b': 9}],
        )

    async def test_edgeql_migration_eq_collections_13(self):
        await self.migrate(r"""
            type Base {
                property foo -> float32;
            };

            # alias that don't have arrays
            alias BaseAlias := Base { bar := Base.foo };
            alias CollAlias := Base.foo;
        """)

        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {
                foo := 13.5,
            }
        """)

        # make sure that the alias initialized correctly
        await self.assert_query_result(
            r"""SELECT BaseAlias{bar};""",
            [{'bar': 13.5}],
        )
        await self.assert_query_result(
            r"""SELECT CollAlias;""",
            [13.5],
        )

        await self.migrate(r"""
            type Base {
                property foo -> float32;
            };

            # "same" alias that now have arrays
            alias BaseAlias := Base { bar := [Base.foo] };
            alias CollAlias := [Base.foo];
        """)

        await self.assert_query_result(
            r"""SELECT BaseAlias{bar};""",
            [{'bar': [13.5]}],
        )
        await self.assert_query_result(
            r"""SELECT CollAlias;""",
            [[13.5]],
        )

    async def test_edgeql_migration_eq_collections_14(self):
        await self.migrate(r"""
            type Base {
                property name -> str;
                property foo -> float32;
            };

            # alias that don't have tuples
            alias BaseAlias := Base { bar := Base.foo };
            alias CollAlias := Base.foo;
        """)

        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {
                name := 'coll_14',
                foo := 14.5,
            }
        """)

        # make sure that the alias initialized correctly
        await self.assert_query_result(
            r"""SELECT BaseAlias{bar};""",
            [{'bar': 14.5}],
        )
        await self.assert_query_result(
            r"""SELECT CollAlias;""",
            [14.5],
        )

        await self.migrate(r"""
            type Base {
                property name -> str;
                property foo -> float32;
            };

            # "same" alias that now have tuples
            alias BaseAlias := Base { bar := (Base.name, Base.foo) };
            alias CollAlias := (Base.name, Base.foo);
        """)

        await self.assert_query_result(
            r"""SELECT BaseAlias{bar};""",
            [{'bar': ['coll_14', 14.5]}],
        )
        await self.assert_query_result(
            r"""SELECT CollAlias;""",
            [['coll_14', 14.5]],
        )

    async def test_edgeql_migration_eq_collections_15(self):
        await self.migrate(r"""
            type Base {
                property name -> str;
                property number -> int32;
                property foo -> float32;
            };

            # alias that don't have nested collections
            alias BaseAlias := Base { bar := Base.foo };
            alias CollAlias := Base.foo;
        """)

        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {
                name := 'coll_15',
                number := 15,
                foo := 15.5,
            }
        """)

        # make sure that the alias initialized correctly
        await self.assert_query_result(
            r"""SELECT BaseAlias{bar};""",
            [{'bar': 15.5}],
        )
        await self.assert_query_result(
            r"""SELECT CollAlias;""",
            [15.5],
        )

        await self.migrate(r"""
            type Base {
                property name -> str;
                property number -> int32;
                property foo -> float32;
            };

            # "same" alias that now have nested collections
            alias BaseAlias := Base {
                bar := (Base.name, Base.number, [Base.foo])
            };
            alias CollAlias := (Base.name, Base.number, [Base.foo]);
        """)

        await self.assert_query_result(
            r"""SELECT BaseAlias{bar};""",
            [{'bar': ['coll_15', 15, [15.5]]}],
        )
        await self.assert_query_result(
            r"""SELECT CollAlias;""",
            [['coll_15', 15, [15.5]]],
        )

    async def test_edgeql_migration_eq_collections_16(self):
        await self.migrate(r"""
            type Base {
                property name -> str;
                property foo -> float32;
            };

            # alias that don't have named tuples
            alias BaseAlias := Base { bar := Base.foo };
            alias CollAlias := Base.foo;
        """)

        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {
                name := 'coll_16',
                foo := 16.5,
            }
        """)

        # make sure that the alias initialized correctly
        await self.assert_query_result(
            r"""SELECT BaseAlias{bar};""",
            [{'bar': 16.5}],
        )
        await self.assert_query_result(
            r"""SELECT CollAlias;""",
            [16.5],
        )

        await self.migrate(r"""
            type Base {
                property name -> str;
                property foo -> float32;
            };

            # "same" alias that now have named tuples
            alias BaseAlias := Base {
                bar := (a := Base.name, b := Base.foo)
            };
            alias CollAlias := (a := Base.name, b := Base.foo);
        """)

        await self.assert_query_result(
            r"""SELECT BaseAlias{bar};""",
            [{'bar': {'a': 'coll_16', 'b': 16.5}}],
        )
        await self.assert_query_result(
            r"""SELECT CollAlias;""",
            [{'a': 'coll_16', 'b': 16.5}],
        )

    async def test_edgeql_migration_eq_collections_17(self):
        await self.migrate(r"""
            type Base {
                property foo -> float32;
                property bar -> int32;
            };

            # alias with array<int32>
            alias BaseAlias := Base { data := [Base.bar] };
            alias CollAlias := [Base.bar];
        """)

        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {
                foo := 17.5,
                bar := 17,
            }
        """)

        # make sure that the alias initialized correctly
        await self.assert_query_result(
            r"""SELECT BaseAlias{data};""",
            [{'data': [17]}],
        )
        await self.assert_query_result(
            r"""SELECT CollAlias;""",
            [[17]],
        )

        await self.migrate(r"""
            type Base {
                property foo -> float32;
                property bar -> int32;
            };

            # alias with array<float32>
            alias BaseAlias := Base { data := [Base.foo] };
            alias CollAlias := [Base.foo];
        """)

        await self.assert_query_result(
            r"""SELECT BaseAlias{data};""",
            [{'data': [17.5]}],
        )
        await self.assert_query_result(
            r"""SELECT CollAlias;""",
            [[17.5]],
        )

    async def test_edgeql_migration_eq_collections_18(self):
        await self.migrate(r"""
            type Base {
                property name -> str;
                property number -> int32;
                property foo -> float32;
            };

            # alias with tuple<str, int32>
            alias BaseAlias := Base {
                data := (Base.name, Base.number)
            };
            alias CollAlias := (Base.name, Base.number);
        """)

        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {
                name := 'coll_18',
                number := 18,
                foo := 18.5,
            }
        """)

        # make sure that the alias initialized correctly
        await self.assert_query_result(
            r"""SELECT BaseAlias{data};""",
            [{'data': ['coll_18', 18]}],
        )
        await self.assert_query_result(
            r"""SELECT CollAlias;""",
            [['coll_18', 18]],
        )

        await self.migrate(r"""
            type Base {
                property name -> str;
                property number -> int32;
                property foo -> float32;
            };

            # alias with tuple<str, int32, float32>
            alias BaseAlias := Base {
                data := (Base.name, Base.number, Base.foo)
            };
            alias CollAlias := (Base.name, Base.number, Base.foo);
        """)

        await self.assert_query_result(
            r"""SELECT BaseAlias{data};""",
            [{'data': ['coll_18', 18, 18.5]}],
        )
        await self.assert_query_result(
            r"""SELECT CollAlias;""",
            [['coll_18', 18, 18.5]],
        )

    async def test_edgeql_migration_eq_collections_20(self):
        await self.migrate(r"""
            type Base {
                property name -> str;
                property number -> int32;
                property foo -> float32;
            };

            # alias with tuple<str, int32>
            alias BaseAlias := Base {
                data := (Base.name, Base.number)
            };
            alias CollAlias := (Base.name, Base.number);
        """)

        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {
                name := 'test20',
                number := 20,
                foo := 123.5,
            }
        """)

        # make sure that the alias initialized correctly
        await self.assert_query_result(
            r"""SELECT BaseAlias{data};""",
            [{'data': ['test20', 20]}],
        )
        await self.assert_query_result(
            r"""SELECT CollAlias;""",
            [['test20', 20]],
        )

        await self.migrate(r"""
            type Base {
                property name -> str;
                property number -> int32;
                property foo -> float32;
            };

            # alias with tuple<str, float32>
            alias BaseAlias := Base {
                data := (Base.name, Base.foo)
            };
            alias CollAlias := (Base.name, Base.foo);
        """)

        await self.assert_query_result(
            r"""SELECT BaseAlias {data};""",
            [{'data': ['test20', 123.5]}],
        )
        await self.assert_query_result(
            r"""SELECT CollAlias;""",
            [['test20', 123.5]],
        )

    async def test_edgeql_migration_eq_collections_21(self):
        await self.migrate(r"""
            type Base {
                property name -> str;
                property foo -> float32;
            };

            # alias with tuple<str, float32>
            alias BaseAlias := Base {
                data := (Base.name, Base.foo)
            };
            alias CollAlias := (Base.name, Base.foo);
        """)

        await self.con.execute(r"""
            SET MODULE test;

            INSERT Base {
                name := 'coll_21',
                foo := 21.5,
            }
        """)

        # make sure that the alias initialized correctly
        await self.assert_query_result(
            r"""SELECT BaseAlias{data};""",
            [{'data': ['coll_21', 21.5]}],
        )
        await self.assert_query_result(
            r"""SELECT CollAlias;""",
            [['coll_21', 21.5]],
        )

        await self.migrate(r"""
            type Base {
                property name -> str;
                property foo -> float32;
            };

            # alias with named tuple<a: str, b: float32>
            alias BaseAlias := Base {
                data := (a := Base.name, b := Base.foo)
            };
            alias CollAlias := (a := Base.name, b := Base.foo);
        """)

        await self.assert_query_result(
            r"""SELECT BaseAlias{data};""",
            [{'data': {'a': 'coll_21', 'b': 21.5}}],
        )
        await self.assert_query_result(
            r"""SELECT CollAlias;""",
            [{'a': 'coll_21', 'b': 21.5}],
        )

    async def test_edgeql_migration_eq_drop_module(self):
        await self.migrate(r"""
            type Base;
        """, module='test')

        await self.migrate(r"""
            scalar type foo extending std::str;
        """, module='newtest')

        await self.assert_query_result(
            'SELECT (SELECT schema::Module FILTER .name LIKE "%test").name;',
            {'newtest', 'std::_test'},
        )

    async def test_edgeql_migration_inherited_optionality_01(self):
        await self.migrate(r"""
            type User;

            type Message {
                required link author -> User;
                required property body -> str;
            };
        """)

        await self.start_migration(r"""
            type User;

            type BaseMessage {
                required link author -> User;
                required property body -> str;
            }

            type Message extending BaseMessage;
        """)

        await self.fast_forward_describe_migration()

    async def test_edgeql_migration_rename_type_02(self):
        await self.migrate(r"""
            type Note {
                property note -> str;
            }
            type Subtype extending Note;
            type Link {
                link a -> Note;
            }
            type Uses {
                required property x -> str {
                    default := (SELECT Note.note LIMIT 1)
                }
            };
            type ComputeLink {
                property foo -> str;
                multi link x := (
                    SELECT Note FILTER Note.note = ComputeLink.foo);
            };
            alias Alias := Note;
        """)

        await self.migrate(r"""
            type Remark {
                property note -> str;
            }
            type Subtype extending Remark;
            type Link {
                link a -> Remark;
            }
            type Uses {
                required property x -> str {
                    default := (SELECT Remark.note LIMIT 1)
                }
            };
            type ComputeLink {
                property foo -> str;
                multi link x := (
                    SELECT Remark FILTER Remark.note = ComputeLink.foo);
            };
            alias Alias := Remark;
        """)

        await self.migrate("")

    async def test_edgeql_migration_rename_type_03(self):
        await self.migrate(r"""
            type Note {
                property note -> str;
            }
        """)

        await self.migrate(r"""
            type Remark {
                property note -> str;
            }
            type Subtype extending Remark;
            type Link {
                link a -> Remark;
            }
            type Uses {
                required property x -> str {
                    default := (SELECT Remark.note LIMIT 1)
                }
            };
            type ComputeLink {
                property foo -> str;
                multi link x := (
                    SELECT Remark FILTER Remark.note = ComputeLink.foo);
            };
            alias Alias := Remark;
        """)

        await self.migrate("")

    async def test_edgeql_migration_annotation_05(self):
        await self.migrate(r"""
            abstract inheritable annotation my_anno;

            type Base {
                property my_prop -> str {
                    annotation my_anno := 'Base my_anno 05';
                }
            }

            type Derived extending Base {
                overloaded property my_prop -> str {
                    annotation my_anno := 'Derived my_anno 05';
                }
            }
        """)

        await self.migrate(r"""
            # rename annotated & inherited property
            abstract inheritable annotation my_anno;

            type Base {
                property renamed_prop -> str {
                    annotation my_anno := 'Base my_anno 05';
                }
            }

            type Derived extending Base {
                overloaded property renamed_prop -> str {
                    annotation my_anno := 'Derived my_anno 05';
                }
            }
        """)

        await self.migrate("")

    async def test_edgeql_migration_reset_optional_01(self):
        await self.migrate(r'''
            abstract type Person {
                required property name -> str;
            }

            type PC extending Person;
        ''')

        await self.migrate(r'''
            abstract type Person {
                property name -> str;
            }

            type PC extending Person;
        ''')

        await self.migrate(r'''
            abstract type Person {
                required property name -> str;
            }

            type PC extending Person;
        ''', user_input=['""'])

    async def test_edgeql_migration_reset_optional_02(self):
        await self.migrate(r'''
            abstract type Person {
                required property name -> str;
            }

            type PC extending Person {
                overloaded required property name -> str;
            }
        ''')

        await self.migrate(r'''
            abstract type Person {
                property name -> str;
            }

            type PC extending Person {
                overloaded property name -> str;
            }
        ''')

    async def test_edgeql_migration_reset_optional_03(self):
        await self.migrate(r'''
            abstract type Person {
                required property name -> str;
            }

            type PC extending Person {
                overloaded required property name -> str;
            }
        ''')

        await self.migrate(r'''
            abstract type Person {
                optional property name -> str;
            }

            type PC extending Person {
                overloaded optional property name -> str;
            }
        ''')

    @test.xerror('''
        This fails because we try to set the parent as required while the
        child is still optional.

        For this to work, we need to process the *child* first,
        but in order for the reverse case above to work we need to
        process the *parent* first.

        I don't know if there is any way to fix this sort of thing without
        exposing this kind of semantic understanding to ordering.

        Maybe suppressing some kinds of intermediate-state errors during
        migrations would be OK?
    ''')
    async def test_edgeql_migration_reset_optional_04(self):
        await self.migrate(r'''
            abstract type Person {
                optional property name -> str;
            }

            type PC extending Person {
                overloaded optional property name -> str;
            }
        ''')

        await self.migrate(r'''
            abstract type Person {
                required property name -> str;
            }

            type PC extending Person {
                overloaded required property name -> str;
            }
        ''', user_input=["''", "''"])

    async def test_edgeql_migration_invalid_scalar_01(self):
        with self.assertRaisesRegex(
                edgedb.SchemaError,
                r"may not have more than one concrete base type"):
            await self.con.execute(r"""
                START MIGRATION TO {
                    abstract scalar type test::lol extending str;
                    scalar type test::myint extending int64, test::lol;
                };
                POPULATE MIGRATION;
            """)

    async def test_edgeql_migration_inherited_default_01(self):
        await self.migrate(r"""
            abstract type Foo {
                multi link link -> Obj {
                    default := ( select Obj filter .name = 'X' )
                };
            }

            type Bar extending Foo {}

            type Obj {
                required property name -> str {
                    constraint exclusive;
                }
            }
        """)

    async def test_edgeql_migration_inherited_default_02(self):
        await self.migrate(r"""
            abstract type Foo {
                multi link link -> Obj {
                };
            }

            type Bar extending Foo {}

            type Obj {
                required property name -> str {
                    constraint exclusive;
                }
            }
        """)

        await self.migrate(r"""
            abstract type Foo {
                multi link link -> Obj {
                    default := ( select Obj filter .name = 'X' )
                };
            }

            type Bar extending Foo {}

            type Obj {
                required property name -> str {
                    constraint exclusive;
                }
            }
        """)

    async def test_edgeql_migration_scalar_array_01(self):
        await self.migrate(r"""
            type User {
                required property scopes -> array<scope>;
            }
            scalar type scope extending int64 {
                constraint one_of (1, 2);
            }
        """)

        await self.migrate(r"""
            type User {
                required property scopes -> array<scope>;
            }
            scalar type scope extending int64 {
                constraint one_of (1, 2, 3);
            }
        """)

    async def test_edgeql_migration_scalar_array_02(self):
        await self.migrate(r"""
            scalar type scope extending int64;
        """)

        await self.migrate(r"""
            type User {
                required property scopes -> array<scope>;
            }
            scalar type scope extending int64 {
                constraint one_of (1, 2);
            }
        """)

    async def test_edgeql_migration_force_alter(self):
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Obj1 {
                        property foo -> str;
                        property bar -> str;
                    }

                    type Obj2 {
                        property o -> int64;
                        link o1 -> Obj1;
                    }
                };
            };
        ''')
        await self.fast_forward_describe_migration()

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Obj1 {
                        property foo -> str;
                        property bar -> str;
                    }

                    type NewObj2 {
                        property name -> str;
                        annotation title := 'Obj2';
                    }
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text':
                        'CREATE TYPE test::NewObj2 {\n'
                        "    CREATE ANNOTATION std::title := 'Obj2';\n"
                        '    CREATE PROPERTY name'
                        ': std::str;\n'
                        '};'
                }],
            },
        })

        await self.con.execute('''
            ALTER CURRENT MIGRATION REJECT PROPOSED;
        ''')

        # We get the parts suggested to us granularly. We only bother
        # to check the first one.
        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text':
                        'ALTER TYPE test::Obj2 {\n'
                        '    DROP LINK o1;\n'
                        '\n'
                        '};'
                }],
            },
        })
        await self.fast_forward_describe_migration()

    async def test_edgeql_migration_non_ddl_statements(self):
        await self.con.execute('SET MODULE test')

        await self.start_migration('''
            type Obj1 {
                property foo -> str;
            }
        ''')

        await self.con.execute('SELECT 1')

        await self.fast_forward_describe_migration(commit=False)

        await self.con.execute('INSERT Obj1 { foo := "test" }')

        await self.assert_describe_migration({
            'confirmed': [
                'SELECT 1;',
                'CREATE TYPE test::Obj1 { CREATE PROPERTY foo: std::str; };',
                "INSERT Obj1 { foo := 'test' };"
            ],
            'complete': True,
        })

        await self.con.execute('COMMIT MIGRATION')

        await self.assert_query_result(
            'SELECT Obj1 { foo }',
            [{'foo': 'test'}],
        )

    async def test_edgeql_migration_future_01(self):
        await self.con.execute('''
            START MIGRATION TO {
                using future nonrecursive_access_policies;
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text':
                        "CREATE FUTURE nonrecursive_access_policies;"
                }],
                'confidence': 1.0,
            },
        })

        await self.fast_forward_describe_migration()

        await self.assert_query_result(
            r"""
                SELECT schema::FutureBehavior {
                    name,
                }
                FILTER .name = 'nonrecursive_access_policies'
            """,
            [{
                'name': 'nonrecursive_access_policies',
            }]
        )

        await self.con.execute('''
            START MIGRATION TO {
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text':
                        'DROP FUTURE nonrecursive_access_policies;'
                }],
                'confidence': 1.0,
            },
        })

        await self.fast_forward_describe_migration()

        await self.assert_query_result(
            r"""
                SELECT schema::FutureBehavior {
                    name,
                }
                FILTER .name = 'nonrecursive_access_policies'
            """,
            []
        )

    async def test_edgeql_migration_extensions_01(self):
        await self.con.execute('''
            START MIGRATION TO {
                using extension graphql;
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text':
                        "CREATE EXTENSION graphql VERSION '1.0';"
                }],
                'confidence': 1.0,
            },
        })

        await self.fast_forward_describe_migration()

        await self.assert_query_result(
            r"""
                SELECT schema::Extension {
                    name,
                }
                FILTER .name = 'graphql'
            """,
            [{
                'name': 'graphql',
            }]
        )

        await self.con.execute('''
            START MIGRATION TO {
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text':
                        'DROP EXTENSION graphql;'
                }],
                'confidence': 1.0,
            },
        })

        await self.fast_forward_describe_migration()

        await self.assert_query_result(
            r"""
                SELECT schema::Extension {
                    name,
                }
                FILTER .name = 'graphql'
            """,
            [],
        )

        await self.con.execute('''
            START MIGRATION TO {
                using extension graphql version '1.0';
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text':
                        "CREATE EXTENSION graphql VERSION '1.0';"
                }],
                'confidence': 1.0,
            },
        })

        await self.fast_forward_describe_migration()

    async def test_edgeql_migration_confidence_01(self):
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Obj1 {
                        property foo -> str;
                        property bar -> str;
                    }
                };
            };
        ''')
        await self.fast_forward_describe_migration()

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type NewObj1 {
                        property foo -> str;
                        property bar -> str;
                    }
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text':
                        'ALTER TYPE test::Obj1 RENAME TO test::NewObj1;'
                }],
                'confidence': 0.637027,
            },
        })

        await self.con.execute('''
            ALTER CURRENT MIGRATION REJECT PROPOSED;
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text':
                        'CREATE TYPE test::NewObj1 {\n'
                        '    CREATE PROPERTY bar: std::str;'
                        '\n    CREATE PROPERTY foo: std::str;'
                        '\n};'
                }],
                'confidence': 1.0,
            },
        })

    async def test_edgeql_migration_confidence_02(self):
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Obj1;
                };
            };
        ''')
        await self.fast_forward_describe_migration()

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Obj1;
                    type Obj2;
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text':
                        'CREATE TYPE test::Obj2;'
                }],
                'confidence': 1.0,
            },
        })

    async def test_edgeql_migration_confidence_03(self):
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Obj1;
                };
            };
        ''')
        await self.fast_forward_describe_migration()

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Obj1 {
                        property x -> str;
                    }
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text':
                        'ALTER TYPE test::Obj1 {\n    '
                        'CREATE PROPERTY x: std::str;\n};'
                }],
                'confidence': 1.0,
            },
        })

    async def test_edgeql_migration_confidence_04(self):
        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Obj1 {
                        link link -> Object;
                    }
                };
            };
        ''')
        await self.fast_forward_describe_migration()

        await self.con.execute('''
            START MIGRATION TO {
                module test {
                    type Obj1 {
                        link link -> Object {
                            property x -> str;
                         }
                    }
                };
            };
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'complete': False,
            'proposed': {
                'statements': [{
                    'text':
                        'ALTER TYPE test::Obj1 {\n    '
                        'ALTER LINK link {\n        '
                        'CREATE PROPERTY x: std::str;\n    };\n};'
                }],
                'confidence': 1.0,
            },
        })

    async def test_edgeql_migration_data_safety_01(self):
        await self.start_migration('''
            type Obj1;
        ''')
        await self.fast_forward_describe_migration()

        await self.start_migration('''
            type Obj1;
            type Obj2;
        ''')

        # Creations are safe
        await self.assert_describe_migration({
            'confirmed': [],
            'proposed': {
                'statements': [{
                    'text':
                        'CREATE TYPE test::Obj2;'
                }],
                'data_safe': True,
            },
        })

        await self.fast_forward_describe_migration()

        await self.start_migration('''
            type Obj1;
        ''')

        # Deletions are NOT safe
        await self.assert_describe_migration({
            'confirmed': [],
            'proposed': {
                'statements': [{
                    'text': 'DROP TYPE test::Obj2;'
                }],
                'data_safe': False,
            },
        })

        await self.fast_forward_describe_migration()

        # Renames are safe
        await self.start_migration('''
            type Obj11;
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'proposed': {
                'statements': [{
                    'text': 'ALTER TYPE test::Obj1 RENAME TO test::Obj11;'
                }],
                'data_safe': True,
            },
        })

        await self.fast_forward_describe_migration()

        # Again, creations are safe.
        await self.start_migration('''
            type Obj11 {
                property name -> str {
                    constraint exclusive;
                }
            }
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'proposed': {
                'statements': [{
                    'text': """
                        ALTER TYPE test::Obj11 {
                            CREATE PROPERTY name: std::str {
                                CREATE CONSTRAINT std::exclusive;
                            };
                        };
                    """,
                }],
                'data_safe': True,
            },
        })

        await self.fast_forward_describe_migration()

        await self.start_migration('''
            type Obj11 {
                property name -> str;
            }
        ''')

        # Dropping constraints is safe.
        await self.assert_describe_migration({
            'confirmed': [],
            'proposed': {
                'statements': [{
                    'text': """
                        ALTER TYPE test::Obj11 {
                            ALTER PROPERTY name {
                                DROP CONSTRAINT std::exclusive;
                            };
                        };
                    """,
                }],
                'data_safe': True,
            },
        })

        await self.fast_forward_describe_migration()

        await self.start_migration('''
            type Obj11 {
                property name -> str {
                    annotation title := 'name';
                }
            }
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'proposed': {
                'statements': [{
                    'text': """
                        ALTER TYPE test::Obj11 {
                            ALTER PROPERTY name {
                                CREATE ANNOTATION std::title := 'name';
                            };
                        };
                    """,
                }],
                'data_safe': True,
            },
        })

        await self.fast_forward_describe_migration()

        # Dropping annotations is fine also.
        await self.start_migration('''
            type Obj11 {
                property name -> str;
            }
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'proposed': {
                'statements': [{
                    'text': """
                        ALTER TYPE test::Obj11 {
                            ALTER PROPERTY name {
                                DROP ANNOTATION std::title;
                            };
                        };
                    """,
                }],
                'data_safe': True,
            },
        })

        await self.fast_forward_describe_migration()

        await self.start_migration('''
            scalar type foo extending str;
            type Obj11 {
                property name -> str;
            }
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'proposed': {
                'statements': [{
                    'text': "CREATE SCALAR TYPE test::foo EXTENDING std::str;",
                }],
                'data_safe': True,
            },
        })

        await self.fast_forward_describe_migration()

        # Dropping scalar types is fine also.
        await self.start_migration('''
            type Obj11 {
                property name -> str;
            }
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'proposed': {
                'statements': [{
                    'text': "DROP SCALAR TYPE test::foo;",
                }],
                'data_safe': True,
            },
        })

        await self.fast_forward_describe_migration()

        await self.start_migration('''
            type Obj11 {
                property name -> str;
                index on (.name);
            }
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'proposed': {
                'statements': [{
                    'text': """
                        ALTER TYPE test::Obj11 {
                            CREATE INDEX ON (.name);
                        };
                    """,
                }],
                'data_safe': True,
            },
        })

        await self.fast_forward_describe_migration()

        # Dropping indexes is fine also.
        await self.start_migration('''
            type Obj11 {
                property name -> str;
            }
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'proposed': {
                'statements': [{
                    'text': """
                        ALTER TYPE test::Obj11 {
                            DROP INDEX ON (.name);
                        };
                    """,
                }],
                'data_safe': True,
            },
        })

        await self.fast_forward_describe_migration()

        # Changing single to multi is fine.
        await self.start_migration('''
            type Obj11 {
                multi property name -> str;
            }
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'proposed': {
                'statements': [{
                    'text': """
                        ALTER TYPE test::Obj11 {
                            ALTER PROPERTY name {
                                SET MULTI;
                            };
                        };
                    """,
                }],
                'data_safe': True,
            },
        })

        await self.fast_forward_describe_migration()

        # But changing multi to single is NOT
        await self.start_migration('''
            type Obj11 {
                single property name -> str;
            }
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'proposed': {
                'statements': [{
                    'text': r"""
                        ALTER TYPE test::Obj11 {
                            ALTER PROPERTY name {
                                SET SINGLE USING (\(conv_expr));
                            };
                        };
                    """,
                }],
                'data_safe': False,
            },
        })

        await self.fast_forward_describe_migration(
            user_input=[
                '(SELECT .name LIMIT 1)'
            ]
        )

        # And changing a property to computed is NOT!
        await self.start_migration('''
            type Obj11 {
                single property name := "test";
            }
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'proposed': {
                'data_safe': False,
            },
        })

        await self.fast_forward_describe_migration()

        # But just changing a computed is
        await self.start_migration('''
            type Obj11 {
                single property name := "fffff";
            }
        ''')

        await self.assert_describe_migration({
            'confirmed': [],
            'proposed': {
                'data_safe': True,
            },
        })

        await self.fast_forward_describe_migration()

    async def test_edgeql_migration_prompt_id_01(self):
        await self.start_migration('''
            type Bar { link spam -> Spam };
            type Spam { link bar -> Bar };
        ''')

        await self.assert_describe_migration({
            'proposed': {
                'prompt_id': 'CreateObjectType TYPE test::Bar',
                'statements': [{
                    'text': 'CREATE TYPE test::Bar;'
                }],
                'confidence': 1.0,
            },
        })

        await self.fast_forward_describe_migration(limit=1)

        await self.assert_describe_migration({
            'proposed': {
                'prompt_id': 'CreateObjectType TYPE test::Spam',
                'statements': [{
                    'text': """
                        CREATE TYPE test::Spam {
                            CREATE LINK bar: test::Bar;
                        };
                    """,
                }],
                'confidence': 1.0,
            },
        })

        await self.fast_forward_describe_migration(limit=1)

        # N.B: It is important that the prompt_id here match the
        # prompt_id in the first migration, so that the migration tool
        # will automatically apply this proposal as part of the
        # earlier action.
        await self.assert_describe_migration({
            'proposed': {
                'prompt_id': 'CreateObjectType TYPE test::Bar',
                'statements': [{
                    'text': """
                        ALTER TYPE test::Bar {
                            CREATE LINK spam: test::Spam;
                        };
                    """,
                }],
                'confidence': 1.0,
            },
        })

    async def test_edgeql_migration_user_input_01(self):
        await self.migrate('''
            type Bar { property foo -> str };
        ''')

        await self.start_migration('''
            type Bar { property foo -> int64 };
        ''')

        await self.assert_describe_migration({
            'proposed': {
                'statements': [{
                    'text': '''
                        ALTER TYPE test::Bar {
                            ALTER PROPERTY foo {
                                SET TYPE std::int64 USING (\\(cast_expr));
                            };
                        };
                    '''
                }],
                'required_user_input': [{
                    'placeholder': 'cast_expr',
                    'prompt': (
                        "Please specify a conversion expression"
                        " to alter the type of property 'foo'"
                    ),
                    'old_type': 'std::str',
                    'new_type': 'std::int64',
                    'pointer_name': 'foo',
                }],
            },
        })

    async def test_edgeql_migration_user_input_02(self):
        await self.migrate('''
            type Bar { multi property foo -> str };
        ''')

        await self.start_migration('''
            type Bar { single property foo -> str };
        ''')

        await self.assert_describe_migration({
            'proposed': {
                'statements': [{
                    'text': '''
                        ALTER TYPE test::Bar {
                            ALTER PROPERTY foo {
                                SET SINGLE USING (\\(conv_expr));
                            };
                        };
                    '''
                }],
                'required_user_input': [{
                    'placeholder': 'conv_expr',
                    'prompt': (
                        "Please specify an expression in order to convert"
                        " property 'foo' of object type 'test::Bar' to"
                        " 'single' cardinality"
                    ),
                    'type': 'std::str',
                    'pointer_name': 'foo',
                }],
            },
        })

    async def test_edgeql_migration_user_input_03(self):
        await self.migrate('''
            type Bar {
                required property foo -> int64;
            };
        ''')
        await self.con.execute('''
            SET MODULE test;
            INSERT Bar { foo := 42 };
            INSERT Bar { foo := 1337 };
        ''')

        await self.start_migration('''
            type Bar {
                required property foo -> int64;
                required property bar -> str;
            };
        ''')

        await self.assert_describe_migration({
            'proposed': {
                'statements': [{
                    'text': '''
                        ALTER TYPE test::Bar {
                            CREATE REQUIRED PROPERTY bar: std::str {
                                SET REQUIRED USING (\\(fill_expr));
                            };
                        };
                    '''
                }],
                'required_user_input': [{
                    'placeholder': 'fill_expr',
                    'prompt': (
                        "Please specify an expression to populate existing "
                        "objects in order to make property 'bar' of object "
                        "type 'test::Bar' required"
                    ),
                    'type': 'std::str',
                    'pointer_name': 'bar',
                }],
            },
        })

        await self.fast_forward_describe_migration(
            user_input=[
                '<str>.foo ++ "!"'
            ]
        )

        await self.assert_query_result(
            '''
                SELECT Bar {foo, bar} ORDER BY .foo
            ''',
            [
                {'foo': 42, 'bar': "42!"},
                {'foo': 1337, 'bar': "1337!"},
            ],
        )

    async def test_edgeql_migration_user_input_04(self):
        await self.migrate('''
            type BlogPost {
                property title -> str;
            }
        ''')
        await self.con.execute('''
            SET MODULE test;
            INSERT BlogPost { title := "Programming Considered Harmful" }
        ''')

        await self.start_migration('''
            abstract type HasContent {
                required property content -> str;
            }
            type BlogPost extending HasContent {
                property title -> str;
            }
        ''')

        await self.interact([
            "did you create object type 'test::HasContent'?",
            ("did you alter object type 'test::BlogPost'?", "y",
             '"This page intentionally left blank"'),
            # XXX: There is a final follow-up prompt, since the DDL
            # generated above somewhat wrongly leaves 'content' owned
            # by the child. This is kind of wrong, but also *works*, so
            # maybe it's fine for now.
            "did you alter property 'content' of object type "
            "'test::BlogPost'?",
        ])
        await self.fast_forward_describe_migration()

        await self.assert_query_result(
            '''
                SELECT BlogPost {title, content}
            ''',
            [
                {
                    'title': "Programming Considered Harmful",
                    'content': "This page intentionally left blank",
                },
            ],
        )

    async def test_edgeql_migration_user_input_05(self):
        await self.migrate(
            '''
            type Organization {
                required property name -> str;
            }
            type Department {
                required property name -> str;
            }
            '''
        )
        await self.start_migration(
            '''
            type Organization {
                required property name -> str;
            }
            type Department {
                required link org -> Organization;
            };
            '''
        )

        await self.fast_forward_describe_migration(
            user_input=[
                'insert test::Organization { name := "default" }'
            ]
        )

    async def test_edgeql_migration_user_input_06(self):
        await self.migrate('''
            type Bar {
                property foo -> int64;
            };
        ''')
        await self.con.execute('''
            SET MODULE test;
            INSERT Bar { foo := 42 };
            INSERT Bar;
        ''')

        await self.start_migration('''
            type Bar {
                required property foo -> str;
            };
        ''')

        await self.assert_describe_migration({
            'proposed': {
                'required_user_input': [
                    {
                        'placeholder': 'fill_expr',
                        'type': 'std::int64',
                    },
                    {
                        'placeholder': 'cast_expr',
                        'old_type': 'std::int64',
                        'new_type': 'std::str',
                    },
                ],
            },
        })

        await self.fast_forward_describe_migration(
            user_input=[
                "0",
                "<str>.foo",
            ]
        )

        await self.assert_query_result(
            '''
                SELECT Bar {foo} ORDER BY .foo
            ''',
            [
                {'foo': "0"},
                {'foo': "42"},
            ],
        )

    async def test_edgeql_migration_union_01(self):
        async with self.assertRaisesRegexTx(
            edgedb.QueryError,
            "it is illegal to create a type union that causes a "
            "computed property 'deleted' to mix with other versions of the "
            "same property 'deleted'"
        ):
            await self.migrate('''
                type Category {
                    required property title -> str;
                    required property deleted :=
                        EXISTS(.<element[IS DeletionRecord]);
                };
                type Article {
                    required property title -> str;
                    required property deleted :=
                        EXISTS(.<element[IS DeletionRecord]);
                };
                type DeletionRecord {
                    link element -> Article | Category;
                }
            ''')

    async def test_edgeql_migration_union_02(self):
        await self.migrate('''
            type Target1;
            type Target1Child extending Target1;
            type Target2;

            type Source1 {
                link tgt_union_restrict -> Target1 | Target2;
                multi link tgt_union_m2m_del_source -> Target1 | Target2;
            }

            type Source3 extending Source1;
        ''')
        await self.migrate('')

    async def test_edgeql_migration_backlink_01(self):
        await self.migrate('''
            type User {
                link posts := .<user;
            }

            abstract type Action {
                required link user -> User;
            }

            type Post extending Action;
        ''')

        # Make sure that the objects can actually be created and
        # queried.
        await self.con.execute('''
            SET MODULE test;
            INSERT User;
        ''')
        post = await self.con.query_single('''
            INSERT Post {
                user := (SELECT User LIMIT 1),
            };
        ''')

        await self.assert_query_result(
            r'''
                SELECT User{
                    id,
                    posts: {
                        id
                    } LIMIT 1  # this LIMIT is needed as a workaround
                               # for another bug
                }
            ''',
            [
                {
                    'posts': [{'id': str(post.id)}],
                },
            ],
        )

    async def test_edgeql_migration_misplaced_commands(self):
        async with self.assertRaisesRegexTx(
            edgedb.QueryError,
            r"cannot execute ALTER CURRENT MIGRATION"
            r" outside of a migration block",
        ):
            await self.con.execute('''
                ALTER CURRENT MIGRATION REJECT PROPOSED;
            ''')

        async with self.assertRaisesRegexTx(
            edgedb.QueryError,
            r"cannot execute DESCRIBE CURRENT MIGRATION"
            r" outside of a migration block",
        ):
            await self.con.execute('''
                DESCRIBE CURRENT MIGRATION;
            ''')

        async with self.assertRaisesRegexTx(
            edgedb.QueryError,
            r"cannot execute COMMIT MIGRATION"
            r" outside of a migration block",
        ):
            await self.con.execute('''
                COMMIT MIGRATION;
            ''')

        async with self.assertRaisesRegexTx(
            edgedb.QueryError,
            r"cannot execute ABORT MIGRATION"
            r" outside of a migration block",
        ):
            await self.con.execute('''
                ABORT MIGRATION;
            ''')

        async with self.assertRaisesRegexTx(
            edgedb.QueryError,
            r"cannot execute POPULATE MIGRATION"
            r" outside of a migration block",
        ):
            await self.con.execute('''
                POPULATE MIGRATION;
            ''')

        async with self.assertRaisesRegexTx(
            edgedb.QueryError,
            r"cannot execute CREATE DATABASE"
            r" in a migration block",
        ):
            await self.start_migration('type Foo;')
            await self.con.execute('''
                CREATE DATABASE should_not_happen;
            ''')

        async with self.assertRaisesRegexTx(
            edgedb.QueryError,
            r"cannot execute CREATE ROLE"
            r" in a migration block",
        ):
            await self.start_migration('type Foo;')
            await self.con.execute('''
                CREATE ROLE should_not_happen;
            ''')

        async with self.assertRaisesRegexTx(
            edgedb.QueryError,
            r"cannot execute CREATE MIGRATION"
            r" in a migration block",
        ):
            await self.start_migration('type Foo;')
            await self.con.execute('''
                CREATE MIGRATION blah;
            ''')

        async with self.assertRaisesRegexTx(
            edgedb.QueryError,
            r"cannot execute START MIGRATION"
            r" in a migration block",
        ):
            await self.start_migration('type Foo;')
            await self.con.execute('''
                START MIGRATION TO { module test { type Foo; }};
            ''')

        async with self.assertRaisesRegexTx(
            edgedb.QueryError,
            r"cannot execute START TRANSACTION"
            r" in a migration block",
        ):
            await self.start_migration('type Foo;')
            await self.con.query('''
                START TRANSACTION;
            ''')

        async with self.assertRaisesRegexTx(
            edgedb.QueryError,
            r"cannot execute START TRANSACTION"
            r" in a migration block",
        ):
            await self.start_migration('type Foo;')
            await self.con.query('''
                START TRANSACTION;
            ''')

        async with self.assertRaisesRegexTx(
            edgedb.QueryError,
            r"cannot execute CONFIGURE INSTANCE"
            r" in a migration block",
        ):
            await self.start_migration('type Foo;')
            await self.con.execute('''
                CONFIGURE INSTANCE SET _foo := 123;
            ''')

        async with self.assertRaisesRegexTx(
            edgedb.QueryError,
            r"cannot execute CONFIGURE DATABASE"
            r" in a migration block",
        ):
            await self.start_migration('type Foo;')
            await self.con.execute('''
                CONFIGURE CURRENT DATABASE SET _foo := 123;
            ''')

    @test.xerror('''
        Referring to alias unsupported from computable
    ''')
    async def test_edgeql_migration_alias_01(self):
        await self.migrate(r'''
            type Foo {
                property name -> str;
                property comp := Alias;
            };

            alias Alias := {0, 1, 2, 3};
        ''')

        # Make sure that the objects can actually be created and
        # queried.
        await self.con.execute('''
            SET MODULE test;
            INSERT Foo {name := 'foo'};
        ''')

        await self.assert_query_result(
            r'''
                SELECT Foo {
                    name,
                    comp,
                }
            ''',
            [
                {
                    'name': 'foo',
                    'comp': {0, 1, 2, 3},
                },
            ],
        )

    @test.xerror('''
       Referring to alias unsupported from computable
       This is the only test that broke when we disallowed that!
    ''')
    async def test_edgeql_migration_alias_02(self):
        await self.migrate(r'''
            type Foo {
                property name -> str;
                property comp := Alias + 0;
            };

            alias Alias := {0, 1, 2, 3};
        ''')

        # Make sure that the objects can actually be created and
        # queried.
        await self.con.execute('''
            SET MODULE test;
            INSERT Foo {name := 'foo'};
        ''')

        await self.assert_query_result(
            r'''
                SELECT Foo {
                    name,
                    comp,
                }
            ''',
            [
                {
                    'name': 'foo',
                    'comp': {0, 1, 2, 3},
                },
            ],
        )

        await self.migrate(r'''
            type Foo {
                property name -> str;
                property comp := Alias + 0;
            };

            alias Alias := {4, 5};
        ''')

        await self.assert_query_result(
            r'''
                SELECT Foo {
                    name,
                    comp,
                }
            ''',
            [
                {
                    'name': 'foo',
                    'comp': {4, 5},
                },
            ],
        )

    @test.xerror('''
        Referring to alias unsupported from computable
    ''')
    async def test_edgeql_migration_alias_03(self):
        await self.migrate(r'''
            type Foo {
                property name -> str;
                property comp := {Alias, Alias};
            };

            alias Alias := 42;
        ''')

        # Make sure that the objects can actually be created and
        # queried.
        await self.con.execute('''
            SET MODULE test;
            INSERT Foo {name := 'foo'};
        ''')

        await self.assert_query_result(
            r'''
                SELECT Foo {
                    name,
                    comp,
                }
            ''',
            [
                {
                    'name': 'foo',
                    'comp': [42, 42],
                },
            ],
        )

        await self.migrate(r'''
            type Foo {
                property name -> str;
                property comp := {Alias, Alias};
            };

            alias Alias := 'alias';
        ''')

        await self.assert_query_result(
            r'''
                SELECT Foo {
                    name,
                    comp,
                }
            ''',
            [
                {
                    'name': 'foo',
                    'comp': ['alias', 'alias'],
                },
            ],
        )

    @test.xerror('''
        Referring to alias unsupported from computable
    ''')
    async def test_edgeql_migration_alias_04(self):
        # Same as the previous test, but using a single DDL command to
        # migrate.
        await self.migrate(r'''
            type Foo {
                property name -> str;
                property comp := {Alias, Alias};
            };

            alias Alias := 42;
        ''')

        # Make sure that the objects can actually be created and
        # queried.
        await self.con.execute('''
            SET MODULE test;
            INSERT Foo {name := 'foo'};
        ''')

        await self.assert_query_result(
            r'''
                SELECT Foo {
                    name,
                    comp,
                }
            ''',
            [
                {
                    'name': 'foo',
                    'comp': [42, 42],
                },
            ],
        )

        # Instead of using an SDL migration, use a single DDL command.
        await self.con.execute('''
            ALTER ALIAS Alias USING ('alias');
        ''')

        await self.assert_query_result(
            r'''
                SELECT Foo {
                    name,
                    comp,
                }
            ''',
            [
                {
                    'name': 'foo',
                    'comp': ['alias', 'alias'],
                },
            ],
        )

    @test.xerror('''
        Referring to alias unsupported from computable
    ''')
    async def test_edgeql_migration_alias_05(self):
        await self.migrate(r'''
            type Foo {
                property name -> str;
                link comp := Alias;
            };

            type Bar;

            alias Alias := Bar {val := 42};
        ''')

        # Make sure that the objects can actually be created and
        # queried.
        await self.con.execute('''
            SET MODULE test;
            INSERT Bar;
            INSERT Foo {name := 'foo'};
        ''')

        await self.assert_query_result(
            r'''
                SELECT Foo {
                    name,
                    comp: {
                        val
                    },
                }
            ''',
            [
                {
                    'name': 'foo',
                    'comp': {
                        'val': 42,
                    },
                },
            ],
        )

    @test.xerror('''
        Referring to alias unsupported from computable
    ''')
    async def test_edgeql_migration_alias_06(self):
        await self.migrate(r'''
            type Foo {
                property name -> str;
                property comp := Alias.val;
            };

            type Bar;

            alias Alias := Bar {val := 42};
        ''')

        # Make sure that the objects can actually be created and
        # queried.
        await self.con.execute('''
            SET MODULE test;
            INSERT Bar;
            INSERT Foo {name := 'foo'};
        ''')

        await self.assert_query_result(
            r'''
                SELECT Foo {
                    name,
                    comp,
                }
            ''',
            [
                {
                    'name': 'foo',
                    'comp': {42},
                },
            ],
        )

        await self.migrate(r'''
            type Foo {
                property name -> str;
                property comp := Alias.val;
            };

            type Bar;

            alias Alias := Bar {val := 'val'};
        ''')

        await self.assert_query_result(
            r'''
                SELECT Foo {
                    name,
                    comp,
                }
            ''',
            [
                {
                    'name': 'foo',
                    'comp': {'val'},
                },
            ],
        )

    @test.xerror('''
        Referring to alias unsupported from computable
    ''')
    async def test_edgeql_migration_alias_07(self):
        await self.migrate(r'''
            type Foo {
                property name -> str;
                link comp := Alias.alias_link;
            };

            type Bar {
                property val -> str;
            };
            type Fuz {
                property val -> str;
            };

            alias Alias := Bar {
                alias_link := Fuz {
                    alias_comp := 42,
                }
            };
        ''')

        # Make sure that the objects can actually be created and
        # queried.
        await self.con.execute('''
            SET MODULE test;
            INSERT Bar {val := 'bar'};
            INSERT Fuz {val := 'fuz'};
            INSERT Foo {name := 'foo'};
        ''')

        await self.assert_query_result(
            r'''
                SELECT Foo {
                    name,
                    comp: {
                        val,
                        alias_comp,
                    },
                }
            ''',
            [
                {
                    'name': 'foo',
                    'comp': [{
                        'val': 'fuz',
                        'alais_comp': 42,
                    }],
                },
            ],
        )

        await self.migrate(r'''
            type Foo {
                property name -> str;
                link comp := Alias.alias_link;
            };

            type Bar {
                property val -> str;
            };
            type Fuz {
                property val -> str;
            };

            alias Alias := Bar {
                alias_link := Fuz {
                    alias_comp := 42,
                }
            };
        ''')

        await self.assert_query_result(
            r'''
                SELECT Foo {
                    name,
                    comp: {
                        val
                    },
                }
            ''',
            [
                {
                    'name': 'foo',
                    'comp': [{
                        'val': 'bar',
                        'alais_comp': 42,
                    }],
                },
            ],
        )

    async def test_edgeql_migration_alias_08(self):
        await self.migrate(r'''
            type Foo;
            type Bar;
            alias X := Foo;
        ''')

        await self.migrate(r'''
            type Foo;
            type Bar;
            alias X := Bar;
        ''')

        await self.migrate(r'''
            type Foo;
            type Bar;
            alias X := 30;
        ''')

        await self.migrate(r'''
            type Foo;
            type Bar;
            alias X := "30";
        ''')

        await self.migrate(r'''
            type Foo;
            type Bar;
            alias X := (Bar { z := 10 }, 30);
        ''')

        await self.migrate(r'''
            type Foo;
            type Bar;
            alias X := ((Bar { z := 10 }, 30), 20);
        ''')

        # delete it
        await self.migrate(r'''
            type Foo;
            type Bar;
        ''')

    async def test_edgeql_migration_alias_09(self):
        await self.migrate(r'''
            type Foo;
            type Bar;
            alias X := Foo { bar := Bar { z := 1 } };
        ''')

        await self.migrate(r'''
            type Foo;
            type Bar;
            alias X := Bar;
        ''')

    async def test_edgeql_migration_tuple_01(self):
        await self.migrate(r'''
            type Bag {
                property switching_tuple -> tuple<name: str, weight: int64>;
            };
        ''')

        with self.assertRaisesRegex(
            AssertionError,
            r"Please specify a conversion expression to alter the type of "
            r"property 'switching_tuple'"
        ):
            await self.migrate(r'''
                type Bag {
                    property switching_tuple -> tuple<name: str, age: int64>;
                };
            ''')

    async def test_edgeql_migration_inheritance_to_empty_01(self):
        await self.migrate(r'''
            type A {
                property name -> str;
            }
            type B {
                property name -> str;
            }
            type C extending A, B {
            }
        ''')

        await self.migrate('')

    async def test_edgeql_migration_inheritance_to_empty_02(self):
        await self.migrate(r'''
            abstract type Named {
                required property name -> str {
                    delegated constraint exclusive;
                }
            }

            type User {
                link avatar -> Card {
                    property text -> str;
                    property tag := .name ++ (("-" ++ @text) ?? "");
                }
            }

            type Card extending Named;

            type SpecialCard extending Card;
        ''')

        await self.migrate('')

    async def test_edgeql_migration_drop_constraint_01(self):
        await self.migrate(r'''
            abstract type Named {
                required property name -> str {
                    delegated constraint exclusive;
                }
            }

            type User {
                link avatar -> Card {
                    property text -> str;
                    property tag := .name ++ (("-" ++ @text) ?? "");
                }
            }

            type Card extending Named;

            type SpecialCard extending Card;
        ''')

        await self.migrate(r'''
            abstract type Named {
                required property name -> str;
            }

            type User {
                link avatar -> Card {
                    property text -> str;
                    property tag := .name ++ (("-" ++ @text) ?? "");
                }
            }

            type Card extending Named;

            type SpecialCard extending Card;
        ''')

    async def test_edgeql_migration_drop_constraint_02(self):
        await self.migrate(r'''
            abstract type Named {
                required property name -> str {
                    delegated constraint exclusive;
                }
            }

            type User {
                link avatar -> Card {
                    property text -> str;
                    property tag := .name ++ (("-" ++ @text) ?? "");
                }
            }

            type Card extending Named;

            type SpecialCard extending Card;
            type SpecialCard2 extending Card;
            type VerySpecialCard extending SpecialCard, SpecialCard2;
        ''')

        await self.migrate(r'''
            abstract type Named {
                required property name -> str;
            }

            type User {
                link avatar -> Card {
                    property text -> str;
                    property tag := .name ++ (("-" ++ @text) ?? "");
                }
            }

            type Card extending Named;

            type SpecialCard extending Card;
            type SpecialCard2 extending Card;
            type VerySpecialCard extending SpecialCard, SpecialCard2;
        ''')

    async def test_edgeql_migration_drop_constraint_03(self):
        await self.migrate(r'''
            type C {
                required property val -> str {
                    constraint exclusive;
                }
            }

            type Foo {
                required link foo -> C {
                    default := (SELECT C FILTER .val = 'D00');
                }
            }
        ''')

        await self.migrate(r'''
            type Foo {
                required link foo -> C {
                    default := (SELECT C FILTER .val = 'D00');
                }
            }

            type C {
                required property val -> str {
                    constraint exclusive;
                }
            }
        ''')

        await self.migrate('')

    async def test_edgeql_migration_drop_constraint_04(self):
        await self.migrate(r'''
            type C {
                required property val -> str {
                    constraint exclusive;
                }
            }

            type Foo {
                required link foo -> C {
                    default := (SELECT C FILTER .val = 'D00');
                }
            }
        ''')

        await self.migrate(r'''
            type C {
                required property val -> str;
            }

            type Foo {
                required multi link foo -> C {
                    default := (SELECT C FILTER .val = 'D00');
                }
            }
        ''')

    async def test_edgeql_migration_drop_constraint_05(self):
        await self.migrate(r'''
            type C {
                required property val -> str {
                    constraint exclusive;
                }
                required property val2 -> str {
                    constraint exclusive;
                }
            }

            type Foo {
                required link foo -> C {
                    default := (SELECT C FILTER .val = 'D00');
                }
            }
        ''')

        await self.migrate(r'''
            type C {
                required property val2 -> str {
                    constraint exclusive;
                }
            }

            type Foo {
                required link foo -> C {
                    default := (SELECT C FILTER .val2 = 'D00');
                }
            }
        ''')

    async def test_edgeql_migration_fiddly_delete_01(self):
        await self.migrate(r'''
            type Document {
              multi link entries -> Entry {
                constraint exclusive;
              }
              multi link fields := .entries.field;
              required link form -> Form;
            }

            type Entry {
              required link field -> Field;
              required property value -> str;
              link form := .field.form;
            }

            type Field {
              required property name -> str;

              link form := .<fields[IS Form];
            }

            type Form {
              required property name -> str {
                constraint exclusive;
              }

              multi link fields -> Field;
            }
        ''')
        await self.migrate(r'''
            type Entry {
              required link field -> Field;
              required property value -> str;
              link form := .field.form;
            }

            type Field {
              required property name -> str;

              link form := .<fields[IS Form];
            }

            type Form {
              required property name -> str {
                constraint exclusive;
              }

              multi link fields -> Field;
            }
        ''')

    async def test_edgeql_migration_uuid_array_01(self):
        await self.migrate(r'''
            type Foo {
                property x -> array<uuid>;
            }
        ''')

    async def test_edgeql_migration_on_target_delete_01(self):
        await self.migrate(
            r"""
                type User {
                    multi link workspaces -> Workspace {
                        property title -> str;
                        on target delete allow;
                    }
                }

                type Workspace {
                    multi link users := .<workspaces[is User];
                }
            """
        )

        await self.migrate(
            r"""
                type User {
                    multi link workspaces := .<users[is Workspace];
                }

                type Workspace {
                    multi link users -> User {
                        property title -> str;
                        on target delete allow;
                    }
                }
            """
        )

    async def test_edgeql_migration_on_target_delete_02(self):
        await self.migrate(
            r"""
                type Tgt;
                type Foo {
                    link tgt -> Tgt {
                        on target delete allow;
                    }
                }
                type Bar extending Foo {
                    overloaded link tgt -> Tgt {
                        on target delete restrict;
                    }
                }
            """
        )

        await self.con.execute("""
            with module test
            insert Bar { tgt := (insert Tgt) };
        """)

        async with self.assertRaisesRegexTx(
            edgedb.ConstraintViolationError,
            'prohibited by link target policy',
        ):
            await self.con.execute("""
                with module test
                delete Tgt;
            """)

        await self.migrate(
            r"""
                type Tgt;
                type Foo {
                    link tgt -> Tgt {
                        on target delete allow;
                    }
                }
                type Bar extending Foo;
            """
        )

        await self.con.execute("""
            with module test
            delete Tgt;
        """)

    async def test_edgeql_migration_rename_with_stuff_01(self):
        await self.migrate(
            r"""
                type Base {
                        property x -> str;
                        property xbang := .x ++ "!";
                }

                type NamedObject extending Base {
                        required property foo -> str;
                }
            """
        )

        await self.migrate(
            r"""
                type Base {
                        property x -> str;
                        property xbang := .x ++ "!";
                }

                type ReNamedObject extending Base {
                        required property foo -> str;
                }
            """
        )

    async def test_edgeql_migration_access_policy_01(self):
        await self.migrate(r"""
            type Test2 {
                access policy asdf allow all using (true);
            }
        """)

        await self.migrate(r"""
            type Test2 {
                access policy asdf allow all;
            }
        """)

        await self.migrate(r"""
            abstract type Parent {
                access policy asdf allow all;
            }
            type Test2 extending Parent;
        """)

        await self.migrate(r"""
            abstract type Parent {
                access policy asdf when (true) allow all;
            }
            type Test2 extending Parent;
        """)

    async def test_edgeql_migration_access_policy_02(self):
        # Make sure policies don't interfere with constraints or indexes
        await self.migrate(r"""
            required global foo -> bool { default := true };
            abstract type Base {
                access policy locked allow all using (global foo);
            }

            type Tgt extending Base;

            type Src {
                required link tgt -> Tgt {
                    constraint exclusive;
                }
                index on (.tgt)
            }
        """)

    async def test_edgeql_migration_globals_01(self):
        schema = r"""
            global current_user_id -> uuid;
            global current_user := (
              select Member filter .id = global current_user_id
            );

            type Foo {
              link owner := .<avatar[is Member];
            };
            type Member {
              link avatar -> Foo {
                constraint exclusive;
              }
            }
        """
        # Make sure it doesn't get into a wedged state
        await self.migrate(schema)
        await self.migrate(schema)

    async def test_edgeql_migration_globals_02(self):
        await self.migrate(r"""
            global current_user_id -> uuid;
            global current_user := (
              select Member filter .id = global current_user_id
            );

            type Foo;
            type Base {
              link avatar -> Foo {
                constraint exclusive;
              }
            }
            type Member;
        """)

        schema = r"""
            global current_user_id -> uuid;
            global current_user := (
              select Member filter .id = global current_user_id
            );

            type Foo;
            type Base {
              link avatar -> Foo {
                constraint exclusive;
              }
            }
            type Member extending Base;
        """

        # Make sure it doesn't get into a wedged state
        await self.migrate(schema)
        await self.migrate(schema)

    async def test_edgeql_migration_globals_03(self):
        # Test modifying a computed global that is used in an access
        # policy on the type it refers to.
        await self.migrate(r"""
            global cur -> uuid;
            global scopes := ((select Foo filter .id = global cur).scopes);

            type Foo {
              multi scopes: str;
              access policy s allow select using ('f' in global scopes);
            };
        """)
        await self.migrate(r"""
            global cur -> uuid;
            global scopes := (
                select (select Foo filter .id = global cur).scopes);

            type Foo {
              multi scopes: str;
              access policy s allow select using ('f' in global scopes);
            };
        """)

    @test.skip('''
        This fails ~half the time depending on how it orders thing
    ''')
    async def test_edgeql_migration_globals_04(self):
        await self.migrate(r"""
            global cur := true;

            type Foo {
              access policy s allow select using (global cur);
            };
            type Bar {
              access policy s allow select using (global cur);
            };
        """)

        await self.migrate(r"""
            global ok := (exists Foo);
            global cur := global ok;

            type Foo {
              access policy s allow select using (global cur);
            };
            type Bar {
              access policy s allow select using (global cur);
            };
        """)

    async def test_edgeql_migration_dml_rewrites_01(self):
        await self.migrate(r"""
            type Post {
              required title: str;
              modified: datetime {
                rewrite insert, update using (datetime_of_statement())
              }
            }
        """)
        await self.migrate(r"""
            type BlogPost {
              required title: str;
              modified: datetime {
                rewrite insert, update using (datetime_of_statement())
              }
            }
        """)
        await self.migrate(r"""
            type BlogPost {
              required title: str;
              modified: datetime {
                rewrite insert, update using (datetime_of_transaction())
              }
            }
        """)
        await self.migrate('')

    async def test_edgeql_migration_policies_and_collections(self):
        # An infinite recursion bug with this this was found by accident
        # when a number of tests accidentally were in the non isolated test.
        # (Simplified a bit.)
        await self.migrate(r"""
            abstract type Base {
                access policy locked allow all using (false);
            }

            type Src {
                required link tgt -> Base {
                    constraint exclusive;
                }
            }
        """)

        await self.migrate(r"""
            alias Foo := 20;
        """)

    async def test_edgeql_migration_drop_required_01(self):
        await self.migrate(r"""
            abstract type AbstractLinkTarget {
                multi link linkSources := .<abstractTarget[is LinkSource];
            }

            type ImplementationType extending AbstractLinkTarget {}

            type LinkSource {
                required link abstractTarget -> AbstractLinkTarget;
            }
        """)

        await self.migrate(r"""
            abstract type AbstractLinkTarget {
                multi link linkSources := .<abstractTarget[is LinkSource];
            }

            type ImplementationType extending AbstractLinkTarget {}

            type LinkSource {
                link abstractTarget -> AbstractLinkTarget;
            }
        """)

    async def test_edgeql_migration_link_to_sub_with_ref_01(self):
        # Test moving a link to a subtype while a ref exists to it
        await self.migrate(r"""
            type Athlete {
                multi link schedules := Athlete.<owner[IS AthleteSchedule];
            }

            abstract type Schedule  {
                required property name -> str;
                required link owner -> Athlete;
            }
            type AthleteSchedule extending Schedule;
        """)

        await self.migrate(r"""
            type Athlete {
                multi link schedules := Athlete.<owner[IS AthleteSchedule];
            }

            abstract type Schedule  {
                required property name -> str;
            }
            type AthleteSchedule extending Schedule {
                required link owner -> Athlete;
            }
        """)

    async def test_edgeql_migration_alias_linkprop_01(self):
        await self.migrate(r"""
            alias UserAlias := User;

            type User {
              multi link ml -> Target {
                property lp -> str;
              };
            }

            type Target;
        """)

    async def test_edgeql_migration_lift_01(self):
        await self.migrate(r"""
            abstract type A;
            abstract type B;

            abstract type Foo extending A;
            type Bar extending Foo, B;
        """)

        await self.migrate(r"""
            abstract type A;
            abstract type B;

            abstract type Foo extending A, B;
            type Bar extending Foo;
        """)

    async def test_edgeql_migration_nested_backticks_01(self):
        await self.migrate(r"""
            module nested { type Test };
        """)

        await self.migrate(r"""
            module nested { type Test };
            module `back``ticked` { type Test };
        """)

    async def test_edgeql_migration_abstract_index_01(self):
        await self.migrate(
            r"""
            abstract index MyIndex extending fts::index;
            type Base {
                property name -> str;
                index MyIndex on (
                    fts::with_options(.name, language := fts::Language.eng)
                );
            };
            """
        )

        await self.migrate(
            r"""
            abstract index MyIndex extending fts::index;
            type Base {
                property name -> str;
                index MyIndex on (
                    fts::with_options(.name, language := fts::Language.eng)
                );
            };
            type Child extending Base;
            """
        )

        async with self.assertRaisesRegexTx(
                edgedb.SchemaError,
                r"because other objects in the schema depend on it"):
            await self.con.execute('''
                drop abstract index test::MyIndex
            ''')

        await self.migrate(
            r"""
            abstract index MyIndex extending fts::index;
            type Base {
                property name -> str;
                index MyIndex on (
                    fts::with_options(.name, language := fts::Language.eng)
                );
            };
            type Child extending Base;
            """
        )

        await self.migrate(
            r"""
            abstract index MyIndex extending fts::index {
                annotation title := "test";
            }
            type Base {
                property name -> str;
                index MyIndex on (
                    fts::with_options(.name, language := fts::Language.eng)
                );
            };
            type Child extending Base;
            """
        )

        await self.migrate("")

    async def test_edgeql_migration_backlink_overloaded(self):
        await self.migrate(r'''
            type Target {
                multi link meta_sources := .<target[is MetaSource];
            }
            abstract type MetaSource {
                link target -> Target;
            }

            type Source extending MetaSource;
            type ExternalSource extending MetaSource {
                overloaded link target -> Target;
            }
        ''')

    async def test_edgeql_migration_property_ref(self):
        await self.migrate(r'''
            type Log {
                body: str;
                timestamp: datetime {
                    default := datetime_current();
                }
            }

            type Person {
                required name: str;
                trigger log_delete after insert for each
                when (__new__.name not like "SKIP%") do (
                    insert Log { body := __new__.name }
                );
            }
        ''')

        await self.migrate(r'''
            type Log {
                body: str;
            }

            type Person {
                required name: str;
                trigger log_delete after insert for each
                when (false) do (
                    insert Log { body := __new__.name }
                );
            }
        ''')

    async def test_edgeql_migration_to_computed_drop_exclusive(self):
        await self.migrate(r'''
            type User {
              multi posts: Post {
                constraint exclusive;
              }
              multi foo: int64 {
                constraint exclusive;
              }
              bar: int64 {
                constraint exclusive;
              }
            }
            type Post;
        ''')

        await self.migrate(r'''
            type User {
              multi link posts := .<user[is Post];
              multi property foo := Post.num;
              property bar := -1;
            }
            type Post {
              user: User;
              num: int64;
            }
        ''')

    async def test_edgeql_migration_between_computeds_01(self):
        await self.migrate(r'''
            type Away {
                x: str;
                property y {
                    using (.x ++ "!");
                    constraint exclusive;
                }
            };
            type Away2 extending Away;
        ''')

        await self.migrate(r'''
            type Away {
                x: str;
                property y -> str {
                    constraint exclusive;
                }
            };
            type Away2 extending Away;
        ''')

    async def test_edgeql_migration_between_computeds_02(self):
        await self.migrate(r'''
            type Away {
                x: str;
                property y {
                    using (.x);
                    constraint exclusive;
                }
            };
            type Away2 extending Away;
        ''')

        await self.migrate(r'''
            type Away {
                x: str;
                property y {
                    using (.x ++ "!");
                    constraint exclusive;
                }
            };
            type Away2 extending Away;
        ''')

        await self.migrate(r'''
            type Away {
                x: str;
                property y {
                    using (.x);
                    constraint exclusive;
                }
            };
            type Away2 extending Away;
        ''')

        await self.migrate(r'''
            type Away {
                x: str;
                property y -> str {
                    constraint exclusive;
                }
            };
            type Away2 extending Away;
        ''')

    async def test_edgeql_migration_alias_new_computed_01(self):
        await self.migrate(r'''
            global a_id -> str;
            global current_user := (
              select User filter .x_id = global a_id);

            type User {
                required property x_id -> str {
                  constraint exclusive;
              }
            }
        ''')

        await self.migrate(r'''
            global a_id -> str;
            global current_user := (
              select User filter .x_id = global a_id);

            type User {
                required property x_id -> str {
                  constraint exclusive;
              }
              required property a_id := .x_id;
            }
        ''')

    async def test_edgeql_migration_alias_new_computed_02(self):
        await self.migrate(r'''
            global a_id -> str;
            alias current_user := (
              select User filter .x_id = global a_id);

            type User {
                required property x_id -> str {
                  constraint exclusive;
              }
            }
        ''')

        await self.migrate(r'''
            global a_id -> str;
            alias current_user := (
              select User filter .x_id = global a_id);

            type User {
                required property x_id -> str {
                  constraint exclusive;
              }
              required property a_id := .x_id;
            }
        ''')

    async def test_edgeql_migration_trigger_shift_01(self):
        await self.migrate(r'''
            type Log {
                body: str;
                timestamp: datetime {
                    default := datetime_current();
                }
            }


            abstract type Named {
                required name: str;
            }

            type Person extending Named {
                trigger log_insert after insert for each when (false) do (
                    insert Log {
                        body := __new__.__type__.name ++ ' ' ++ __new__.name,
                    }
                );
            }
        ''')

        await self.migrate(r'''
            type Log {
                body: str;
                timestamp: datetime {
                    default := datetime_current();
                }
            }


            abstract type Named {
                required name: str;

                trigger log_insert after insert for each when (false) do (
                    insert Log {
                        body := __new__.__type__.name ++ ' ' ++ __new__.name,
                    }
                );
            }

            type Person extending Named {
            }
        ''')

        await self.migrate(r'''
            type Log {
                body: str;
                timestamp: datetime {
                    default := datetime_current();
                }
            }


            abstract type Named {
                required name: str;

                trigger log_insert after insert for each when (true) do (
                    insert Log {
                        body := __new__.__type__.name ++ ' ' ++ __new__.name,
                    }
                );
            }

            type Person extending Named {
            }
        ''')


class TestEdgeQLDataMigrationNonisolated(EdgeQLDataMigrationTestCase):
    TRANSACTION_ISOLATION = False

    PER_TEST_TEARDOWN = [
        'reset schema to initial'
    ]

    async def test_edgeql_migration_eq_collections_25(self):
        await self.con.execute(r"""
            START MIGRATION TO {
                module test {
                    alias Foo := [20];
                }
            };
            POPULATE MIGRATION;
            COMMIT MIGRATION;
        """)

        await self.con.execute(r"""
            START MIGRATION TO {
                module test {
                }
            };
            POPULATE MIGRATION;
            COMMIT MIGRATION;
        """)

    async def test_edgeql_ddl_collection_cleanup_06(self):
        for _ in range(2):
            await self.con.execute(r"""
                CREATE FUNCTION cleanup_06(
                    a: int64
                ) -> tuple<int64, tuple<int64>>
                    USING EdgeQL $$
                        SELECT (a, ((a + 1),))
                    $$;
            """)

            await self.con.execute(r"""
                DROP FUNCTION cleanup_06(a: int64)
            """)

    async def test_edgeql_migration_enum_01(self):
        # Test some enum stuff. This needs to be nonisolated because postgres
        # won't let you *use* an enum until it has been committed!
        await self.migrate('''
            scalar type Status extending enum<pending, in_progress, finished>;
            scalar type ImportStatus extending Status;
            scalar type ImportAnalyticsStatus extending Status;

            type Foo { property x -> ImportStatus };
        ''')
        await self.con.execute('''
            with module test
            insert Foo { x := 'pending' };
        ''')

        await self.migrate('''
            scalar type Status extending enum<
                pending, in_progress, finished, wontfix>;
            scalar type ImportStatus extending Status;
 scalar type ImportAnalyticsStatus extending Status;

            type Foo { property x -> ImportStatus };
            function f(x: Status) -> str USING (<str>x);
        ''')

        await self.migrate('''
            scalar type Status extending enum<
                pending, in_progress, finished, wontfix, again>;
            scalar type ImportStatus extending Status;
            scalar type ImportAnalyticsStatus extending Status;

            type Foo { property x -> ImportStatus };
            function f(x: Status) -> str USING (<str>x);
        ''')

        await self.assert_query_result(
            r"""
                with module test
                select <ImportStatus>'wontfix'
            """,
            ['wontfix'],
        )

        await self.assert_query_result(
            r"""
                with module test
                select f(<ImportStatus>'wontfix')
            """,
            ['wontfix'],
        )

        # Retry for https://github.com/edgedb/edgedb/issues/7553
        async for tr in self.try_until_succeeds(
            ignore_regexp="cannot drop type .* "
                          "because other objects depend on it",
        ):
            async with tr:
                await self.migrate('''
                    scalar type Status extending enum<
                        pending, in_progress, wontfix, again>;
                    scalar type ImportStatus extending Status;
                    scalar type ImportAnalyticsStatus extending Status;

                    type Foo { property x -> ImportStatus };
                    function f(x: Status) -> str USING (<str>x);
                ''')

        await self.migrate('')

    async def test_edgeql_migration_splat_01(self):
        await self.migrate('''
            type Foo {
                property bar := (select <json>Bar { ** })
            }

            type Bar {
                property a -> str;
                link foo -> Foo;
            }
        ''')

        await self.migrate('''
            type Foo {
                property bar := (<json>{})
            }
        ''')

    async def test_edgeql_migration_recovery(self):
        await self.con.execute(r"""
            START MIGRATION TO {
                module test {
                    type Foo;
                }
            };
        """)
        await self.con.execute('POPULATE MIGRATION')

        with self.assertRaises(edgedb.EdgeQLSyntaxError):
            await self.con.execute(r"""
                ALTER TYPE Foo;
            """)

        with self.assertRaises(edgedb.TransactionError):
            await self.con.execute("COMMIT MIGRATION")

        await self.con.execute("ABORT MIGRATION")

        self.assertEqual(await self.con.query_single("SELECT 1"), 1)

    async def test_edgeql_script_partial_migration(self):
        with self.assertRaisesRegex(edgedb.QueryError, "incomplete migration"):
            await self.con.execute(r"""
                START MIGRATION TO {
                    module test {
                        type Foo;
                    }
                };
                POPULATE MIGRATION;
            """)

    async def test_edgeql_migration_recovery_in_tx(self):
        await self.con.execute("START TRANSACTION")
        try:
            await self.con.execute("CREATE TYPE Bar")
            await self.con.execute(r"""
                START MIGRATION TO {
                    module test {
                        type Foo;
                    }
                };
            """)

            with self.assertRaises(edgedb.EdgeQLSyntaxError):
                await self.con.execute(r"""
                    ALTER TYPE Foo;
                """)

            await self.con.execute("ABORT MIGRATION")

            self.assertEqual(await self.con.query("SELECT Bar"), [])
        finally:
            await self.con.execute("ROLLBACK")

    async def test_edgeql_migration_recovery_in_script(self):
        await self.migrate("""
            type Base;
        """)
        await self.con.execute("""
            SET MODULE test;

            INSERT Base;
        """)
        res = await self.con.query(r"""
            CREATE TYPE Bar;
            START MIGRATION TO {
                module test {
                    type Base {
                        required property name -> str;
                    }
                }
            };
            POPULATE MIGRATION;
            ABORT MIGRATION;
            SELECT Bar;
        """)
        self.assertEqual(res, [])

        await self.migrate('')

    async def test_edgeql_migration_recovery_commit_fail(self):
        con2 = await self.connect()
        try:
            with con2.capture_warnings():
                await con2.execute('START MIGRATION TO {}')
            await con2.execute('POPULATE MIGRATION')

            await self.migrate("type Base;")

            with self.assertRaises(edgedb.TransactionError):
                await con2.execute("COMMIT MIGRATION")

            await con2.execute("ROLLBACK")

            self.assertEqual(await con2.query_single("SELECT 1"), 1)
        finally:
            await con2.aclose()

    async def test_edgeql_migration_reset_schema(self):
        await self.migrate(r'''
            type Bar;

            alias Alias := Bar {val := 42};
        ''')
        await self.migrate(r'''
            type Foo {
                property name -> str;
                link comp := Bar;
            };

            type Bar;
        ''')

        res = await self.con.query('''
            select schema::ObjectType { name } filter .name ilike 'test::%'
        ''')
        self.assertEqual(len(res), 2)

        await self.con.query('reset schema to initial')
        await self.assert_last_migration()

        res = await self.con.query('''
            select schema::ObjectType { name } filter .name ilike 'test::%'
        ''')
        self.assertEqual(res, [])

        res = await self.con.query('''
            select schema::Migration { script, name };
        ''')
        self.assertEqual(res, [])

        await self.migrate(r'''
            type SomethingElse;
        ''')

        res = await self.con.query('''
            select schema::Migration { script, name };
        ''')
        self.assertEqual(len(res), 1)

    async def test_edgeql_migration_extension_01(self):
        # Test migrations getting from an array in integers to vector and then
        # revert to an array of floats.

        await self.migrate('''
            module default {
                type Foo {
                    required data: array<int64>
                }
            }
        ''', explicit_modules=True)

        # Populate with some data.
        await self.con.execute('''
            insert Foo {data := [3, 1, 4]};
            insert Foo {data := [5, 6, 0]};
        ''')

        # Add vector and migrate automatically (because we have an assignment
        # cast available from array<int64> to vector).
        await self.migrate('''
            using extension pgvector version '0.5';

            module default {
                scalar type v3 extending ext::pgvector::vector<3>;
                type Foo {
                    property data: v3;
                };
            }
        ''', explicit_modules=True)

        await self.assert_query_result(
            r"""
                select Foo.data
            """,
            tb.bag([[3, 1, 4], [5, 6, 0]]),
            json_only=True,
        )

        await self.migrate(
            '''
            module default {
                type Foo {
                    property data: array<float32>;
                };
            }
            ''',
            explicit_modules=True,
            # This migration needs the conversion expression
            user_input=["<array<float32>>.data"]
        )

        await self.assert_query_result(
            r"""
                select Foo.data
            """,
            tb.bag([[3, 1, 4], [5, 6, 0]]),
        )

    async def _test_schema_repair(
        self, *,
        schema,
        setup_script=None,
        breakage_script,
        check_breakage=True,
    ):
        '''Helper for testing ADMINISTER schema_repair()

        Takes a schema and an optional setup_script and runs them.

        Then runs the breakage_script, which will be run with access
        to the reflschema and which should break the schema in some
        way.

        We will then load back the broken schema, verify migrations
        are broken, run schema_repair, and verify migrations work.
        '''

        await self.migrate(schema, module='default')
        if setup_script:
            await self.con.execute(setup_script)

        # Use a transaction to make sure the configures get rolled back
        async with self.con.transaction():
            # Enable poking directly at the schema
            await self.con.execute('''
                configure session set __internal_query_reflschema := true;
                configure session set __internal_no_apply_query_rewrites :=
                    true;
            ''')

            # Break the schema
            await self.con.execute(breakage_script)

        # Do some random configure current database in order to force
        # a reload of the schema from reflection.
        await self.con.execute("""
            configure current database reset __internal_sess_testvalue
        """)

        # Make sure we succesfully busted the schema.
        async with self.assertRaisesRegexTx(
            # Error could be the 'complete' assertion in this test
            # suite or some failure in the server.
            (AssertionError, edgedb.EdgeDBError),
            '',
        ):
            await self.migrate(schema, module='default')

        await self.con.execute('''
            administer schema_repair()
        ''')

        # Make sure the schema is repaired
        async with self._run_and_rollback():
            await self.start_migration(schema, module='default')
            await self.assert_describe_migration({
                'complete': True
            })

        # Make sure objects still can be fetched
        await self.con.query('''
            select Object
        ''')

    async def test_edgeql_migration_schema_repair_01(self):
        await self._test_schema_repair(
            schema='''
                type T {
                    lol := count(Object);
                    foo: str;
                }
                type S extending T;
            ''',
            setup_script='''
                insert T { foo := "t" };
                insert S { foo := "s" };
            ''',
            breakage_script='''
                update schema::Property
                filter .name__internal = 'default::__|foo@default|S'
                set { required := true };
            ''',
        )

        # Check that a query works and the data is still there
        await self.assert_query_result(
            '''
                select T { foo };
            ''',
            tb.bag([
                {'foo': 't'},
                {'foo': 's'},
            ])
        )

    async def test_edgeql_migration_schema_repair_02(self):
        await self._test_schema_repair(
            schema='''
            abstract type Named {
                index std::fts::index on (
                    std::fts::with_options(
                        .name, language := std::fts::Language.eng));
                required name: std::str;
            };

            type Project0 extending Named;
            ''',
            breakage_script='''
                update schema::Index
                filter .name__internal =
                'default::std|fts|index@default|Project0' ++
                '@bb75b87f16a631d2bd48c6c660d4b52a8b7274cb'
                set { name__internal :=
                    'default::fts|index@default|Project0' ++
                    '@bb75b87f16a631d2bd48c6c660d4b52a8b7274cb'
                }
            ''',
        )


class EdgeQLAIMigrationTestCase(EdgeQLDataMigrationTestCase):
    # AI specific tests, just because creating the extension is so slow,
    # so we want to skip doing that each time.

    DEFAULT_MODULE = 'default'
    PARALLELISM_GRANULARITY = 'default'

    SETUP = '''
        create extension pgvector;
        create extension ai;
        CONFIGURE CURRENT DATABASE
        INSERT ext::ai::OpenAIProviderConfig {
            secret := 'very secret',
            api_url := '',
        };

        CONFIGURE CURRENT DATABASE
            SET ext::ai::Config::indexer_naptime := <duration>'100ms';
    '''

    async def test_edgeql_migration_ai_01(self):
        await self.migrate('''
            using extension ai;

            module default {
              type Astronomy {
                content: str;
                deferred index ext::ai::index(
                  embedding_model := 'text-embedding-3-small'
                ) on (.content);
              }
            };
        ''', explicit_modules=True)

        await self.migrate('''
            using extension ai;
        ''', explicit_modules=True)

    async def test_edgeql_migration_ai_02(self):
        await self.migrate('''
            using extension ai;

            module default {
              type Astronomy {
                content: str;
                deferred index ext::ai::index(
                  embedding_model := 'text-embedding-3-small'
                ) on (.content);
              }
            };
        ''', explicit_modules=True)

        await self.migrate('''
            using extension ai;

            module default {
              type Astronomy {
                content: str;
                deferred index ext::ai::index(
                  embedding_model := 'text-embedding-3-large'
                ) on (.content);
              }
            };
        ''', explicit_modules=True)

        await self.migrate('''
            using extension ai;

            module default {
              type Astronomy {
                content: str;
                deferred index ext::ai::index(
                  embedding_model := 'text-embedding-3-small'
                ) on (.content);
              }
            };
        ''', explicit_modules=True)

    async def test_edgeql_migration_ai_03(self):
        schema = '''
        using extension ai;

        module default {
            type TestEmbeddingModel
                extending ext::ai::EmbeddingModel
            {
                annotation ext::ai::model_name := "text-embedding-test";
                annotation ext::ai::model_provider := "custom::test";
                annotation ext::ai::embedding_model_max_batch_tokens := "16384";
                annotation ext::ai::embedding_model_max_output_dimensions
                  := "10";
                annotation ext::ai::embedding_model_supports_shortening
                  := "true";
            };

            type Astronomy {
                content: str;
                deferred index ext::ai::index(
                    embedding_model := 'text-embedding-test'
                ) on (.content);
            };
        };
        '''

        with self.assertRaisesRegex(
            edgedb.SchemaDefinitionError,
            "object type 'default::TestEmbeddingModel' is missing a value "
            "for the 'ext::ai::embedding_model_max_input_tokens' annotation"
        ):
            await self.migrate(schema, explicit_modules=True)

    @test.xfail('''
        No more "proposed", but not "completed" either.

        It would be OK if we produced a real error here too, though.
    ''')
    async def test_edgeql_migration_ai_04(self):
        # Try changing embedding_model_max_output_dimensions on a
        # custom EmbeddingModel

        await self.migrate('''
            using extension ai;

            module default {
                type TestEmbeddingModel
                    extending ext::ai::EmbeddingModel
                {
                    annotation ext::ai::model_name := "text-embedding-test";
                    annotation ext::ai::model_provider := "custom::test";
                    annotation ext::ai::embedding_model_max_input_tokens
                      := "8191";
                    annotation ext::ai::embedding_model_max_batch_tokens
                      := "16384";
                    annotation ext::ai::embedding_model_max_output_dimensions
                      := "10";
                    annotation ext::ai::embedding_model_supports_shortening
                      := "true";
                };

                type Astronomy {
                    content: str;
                    deferred index ext::ai::index(
                        embedding_model := 'text-embedding-test'
                    ) on (.content);
                };
            };
        ''', explicit_modules=True)

        await self.migrate('''
            using extension ai;

            module default {
                type TestEmbeddingModel
                    extending ext::ai::EmbeddingModel
                {
                    annotation ext::ai::model_name := "text-embedding-test";
                    annotation ext::ai::model_provider := "custom::test";
                    annotation ext::ai::embedding_model_max_input_tokens
                      := "8191";
                    annotation ext::ai::embedding_model_max_batch_tokens
                      := "16384";
                    annotation ext::ai::embedding_model_max_output_dimensions
                      := "20";
                    annotation ext::ai::embedding_model_supports_shortening
                      := "true";
                };

                type Astronomy {
                    content: str;
                    deferred index ext::ai::index(
                        embedding_model := 'text-embedding-test'
                    ) on (.content);
                };
            };
        ''', explicit_modules=True)

    @test.xerror('''
        "undefined embedding model: no subtype of ext::ai::EmbeddingModel is
        annotated as 'text-embedding-test'"
    ''')
    async def test_edgeql_migration_ai_05(self):
        # Try changing the name of a model (and the type, too, though
        # that is not hugely relevant.)

        await self.migrate('''
            using extension ai;

            module default {
                type TestEmbeddingModel
                    extending ext::ai::EmbeddingModel
                {
                    annotation ext::ai::model_name := "text-embedding-test";
                    annotation ext::ai::model_provider := "custom::test";
                    annotation ext::ai::embedding_model_max_input_tokens
                      := "8191";
                    annotation ext::ai::embedding_model_max_batch_tokens
                      := "16384";
                    annotation ext::ai::embedding_model_max_output_dimensions
                      := "10";
                    annotation ext::ai::embedding_model_supports_shortening
                      := "true";
                };

                type Astronomy {
                    content: str;
                    deferred index ext::ai::index(
                        embedding_model := 'text-embedding-test'
                    ) on (.content);
                };
            };
        ''', explicit_modules=True)

        await self.migrate('''
            using extension ai;

            module default {
                type TestEmbeddingModel2
                    extending ext::ai::EmbeddingModel
                {
                    annotation ext::ai::model_name := "text-embedding-test-2";
                    annotation ext::ai::model_provider := "custom::test";
                    annotation ext::ai::embedding_model_max_input_tokens
                      := "8191";
                    annotation ext::ai::embedding_model_max_batch_tokens
                      := "16384";
                    annotation ext::ai::embedding_model_max_output_dimensions
                      := "10";
                    annotation ext::ai::embedding_model_supports_shortening
                      := "true";
                };

                type Astronomy {
                    content: str;
                    deferred index ext::ai::index(
                        embedding_model := 'text-embedding-test'
                    ) on (.content);
                };
            };
        ''', explicit_modules=True)

    async def test_edgeql_migration_ai_06(self):
        # Try putting the EmbeddingModel declaration below the type
        # that uses it.

        await self.migrate('''
            using extension ai;

            module default {
                type Astronomy {
                    content: str;
                    deferred index ext::ai::index(
                        embedding_model := 'text-embedding-test'
                    ) on (.content);
                };

                type TestEmbeddingModel
                    extending ext::ai::EmbeddingModel
                {
                    annotation ext::ai::model_name := "text-embedding-test";
                    annotation ext::ai::model_provider := "custom::test";
                    annotation ext::ai::embedding_model_max_input_tokens
                      := "8191";
                    annotation ext::ai::embedding_model_max_batch_tokens
                      := "16384";
                    annotation ext::ai::embedding_model_max_output_dimensions
                      := "10";
                    annotation ext::ai::embedding_model_supports_shortening
                      := "true";
                };
            };
        ''', explicit_modules=True)

    async def test_edgeql_migration_ai_07a(self):
        await self.migrate('''
            using extension ai;

            module default {
                type Astronomy {
                    content: str;
                    deferred index ext::ai::index(
                        embedding_model := 'text-embedding-3-small'
                    ) on (.content);
                };

                type Sub extending Astronomy {
                    deferred index ext::ai::index(
                        embedding_model := 'text-embedding-3-small'
                    ) on (.content);
                };

            };
        ''', explicit_modules=True)

        await self.migrate('''
            using extension ai;
        ''', explicit_modules=True)

    async def test_edgeql_migration_ai_07b(self):
        await self.migrate('''
            using extension ai;

            module default {
                type Astronomy {
                    content: str;
                    deferred index ext::ai::index(
                        embedding_model := 'text-embedding-3-small'
                    ) on (.content);
                };

                type Sub extending Astronomy;

            };
        ''', explicit_modules=True)

        await self.assert_query_result(
            r"""
                select schema::Index {
                    annotations: {name, @value},
                    subject_name := .<indexes[is schema::ObjectType].name
                }
                filter 'ext::ai::index' IN .ancestors.name
                and .subject_name = 'default::Sub';
            """,
            [
                {
                    "annotations": tb.bag([
                        {"name": "ext::ai::model_name",
                         "@value": "text-embedding-3-small"},
                        {"name": "ext::ai::model_provider",
                         "@value": "builtin::openai"},
                        {"name": "ext::ai::embedding_model_max_input_tokens",
                         "@value": "8191"},
                        {"name": "ext::ai::embedding_model_max_batch_tokens",
                         "@value": "8191"},
                        {
                            "name":
                            "ext::ai::embedding_model_max_output_dimensions",
                            "@value": "1536"
                        },
                        {"name": "ext::ai::embedding_model_supports_shortening",
                         "@value": "true"},
                        {"name": "ext::ai::embedding_dimensions",
                         "@value": "1536"},
                        {'name': 'ext::ai::embedding_model_max_batch_size',
                         '@value': '<optional>'},
                    ]),
                    "subject_name": "default::Sub"
                }
            ]
        )

        await self.migrate('''
            using extension ai;
        ''', explicit_modules=True)

    async def test_edgeql_migration_ai_07c(self):
        await self.migrate('''
            using extension ai;

            module default {
                type Astronomy {
                    content: str;
                    deferred index ext::ai::index(
                        embedding_model := 'text-embedding-3-small'
                    ) on (.content);
                };

                type Sub extending Astronomy {
                };

            };
        ''', explicit_modules=True)

        await self.migrate('''
            using extension ai;

            module default {
                type Astronomy {
                    content: str;
                    deferred index ext::ai::index(
                        embedding_model := 'text-embedding-3-small'
                    ) on (.content);
                };

                type Sub extending Astronomy {
                    deferred index ext::ai::index(
                        embedding_model := 'text-embedding-3-small'
                    ) on (.content);
                };

            };
        ''', explicit_modules=True)

        await self.migrate('''
            using extension ai;

            module default {
                type Astronomy {
                    content: str;
                    deferred index ext::ai::index(
                        embedding_model := 'text-embedding-3-small'
                    ) on (.content);
                };

                type Sub extending Astronomy {
                };

            };
        ''', explicit_modules=True)

        await self.migrate('''
            using extension ai;
        ''', explicit_modules=True)

    async def test_edgeql_migration_ai_08(self):
        await self.migrate('''
            using extension ai;

            module default {
                type Base {
                    content: str;
                    deferred index ext::ai::index(
                        embedding_model := 'text-embedding-3-small'
                    ) on (.content);
                };

                type Sub extending Base {
                    # deferred index ext::ai::index(
                    #     embedding_model := 'text-embedding-3-small'
                    # ) on (.content ++ '!');
                };

            };
        ''', explicit_modules=True)

        arg = [0.0] * 1536
        await self.con.query('''
            select {
                base := ext::ai::search(Base, <array<float32>>$0),
                sub := ext::ai::search(Sub, <array<float32>>$0),
            }
        ''', arg)

        await self.migrate('''
            using extension ai;

            module default {
                type Base {
                    content: str;
                    deferred index ext::ai::index(
                        embedding_model := 'text-embedding-3-small'
                    ) on (.content);
                };

                type Sub extending Base {
                    deferred index ext::ai::index(
                        embedding_model := 'text-embedding-3-small'
                    ) on (.content ++ '!');
                };

            };
        ''', explicit_modules=True)

        await self.con.query('''
            select {
                base := ext::ai::search(Base, <array<float32>>$0),
                sub := ext::ai::search(Sub, <array<float32>>$0),
            }
        ''', arg)

        await self.migrate('''
            using extension ai;

            module default {
                type Base {
                    content: str;
                };

                type Sub extending Base {
                    deferred index ext::ai::index(
                        embedding_model := 'text-embedding-3-small'
                    ) on (.content ++ '!');
                };

            };
        ''', explicit_modules=True)

        # Base lost the index, just select Sub
        await self.con.query('''
            select {
                sub := ext::ai::search(Sub, <array<float32>>$0),
            }
        ''', arg)

    async def test_edgeql_migration_ai_09(self):
        with self.assertRaisesRegex(
            edgedb.InvalidDefinitionError,
            r"with different parameters than on base type",
        ):
            await self.migrate('''
                using extension ai;

                module default {
                    type Base {
                        content: str;
                        deferred index ext::ai::index(
                            embedding_model := 'text-embedding-3-small'
                        ) on (.content);
                    };

                    type Sub extending Base {
                        deferred index ext::ai::index(
                            embedding_model := 'text-embedding-3-large'
                        ) on (.content ++ '!');
                    };

                };
            ''', explicit_modules=True)

    async def test_edgeql_migration_ai_10(self):
        # EmbeddingModel with default embedding_model_max_batch_tokens.

        await self.migrate('''
            using extension ai;

            module default {
                type TestEmbeddingModel
                    extending ext::ai::EmbeddingModel
                {
                    annotation ext::ai::model_name := "text-embedding-test";
                    annotation ext::ai::model_provider := "custom::test";
                    annotation ext::ai::embedding_model_max_input_tokens
                      := "8191";
                    annotation ext::ai::embedding_model_max_output_dimensions
                      := "10";
                    annotation ext::ai::embedding_model_supports_shortening
                      := "true";
                };
            };
        ''', explicit_modules=True)

        await self.assert_query_result(
            r"""
                with model := (
                    select schema::ObjectType {
                        x := (
                            for ann in .annotations
                            select ann@value
                            filter ann.name
                            = 'ext::ai::embedding_model_max_batch_tokens'
                        )
                    }
                    filter .name = 'default::TestEmbeddingModel'
                )
                select model.x
            """,
            ['8191'],
        )


class EdgeQLMigrationRewriteTestCase(EdgeQLDataMigrationTestCase):
    DEFAULT_MODULE = 'default'

    async def migrate(
        self,
        migration: str,
        *,
        module: str | None = 'default',
        **kwargs,
    ):
        await super().migrate(migration, module=module, **kwargs)

    async def get_migrations(self):
        res = await self.con.query(
            '''
            select schema::Migration {
                id, name, script, sdl, parents: {name, id}, generated_by
            }
            '''
        )
        if not res:
            return []
        children = {m.parents[0].id: m for m in res if m.parents}
        root = [m for m in res if not m.parents][0]

        sorted_migs = []
        while root:
            sorted_migs.append(root)
            root = children.get(root.id)

        return sorted_migs

    async def assert_migration_history(self, exp_result):
        res = await self.get_migrations()
        res = serutils.serialize(res)
        assert_data_shape.assert_data_shape(
            res, exp_result, self.fail)
        await self.assert_last_migration()


class TestEdgeQLMigrationRewrite(EdgeQLMigrationRewriteTestCase):
    # N.B: These test cases get duplicated as nonisolated test cases,
    # to verify that it all works *outside* a transaction also.
    # If that is a problem, a test case can be made to skip it

    async def test_edgeql_migration_rewrite_01(self):
        await self.con.execute('''
            CONFIGURE SESSION SET store_migration_sdl :=
                cfg::StoreMigrationSDL.AlwaysStore;
        ''')

        # Split one migration up into several
        await self.migrate(r"""
            type A;
            type B;
            type C;
            type D;
        """)
        # Try a bunch of different ways to do it!
        await self.con.execute(r"""
            start migration rewrite;
            start migration to {
                module default {
                    type A;
                }
            };
            populate migration;
            commit migration;

            start migration to {
                module default {
                    type A;
                    type B;
                }
            };
            CREATE type B;
            commit migration;

            create migration {
                create type C;
            };

            CREATE TYPE D;

            commit migration rewrite;
        """)

        await self.assert_migration_history([
            {
                'script': 'CREATE TYPE default::A;',
                'sdl': (
                    'module default {\n'
                    '    type A;\n'
                    '};'
                ),
                'generated_by': None,
            },
            {
                'script': 'CREATE TYPE B;',
                'sdl': (
                    'module default {\n'
                    '    type A;\n'
                    '    type B;\n'
                    '};'
                ),
                'generated_by': None,
            },
            {
                'script': 'create type C;',
                'sdl': (
                    'module default {\n'
                    '    type A;\n'
                    '    type B;\n'
                    '    type C;\n'
                    '};'
                ),
                'generated_by': None,
            },
            {
                'script': (
                    'SET generated_by '
                    ':= (schema::MigrationGeneratedBy.DDLStatement);\n'
                    'CREATE TYPE D;'
                ),
                'sdl': (
                    'module default {\n'
                    '    type A;\n'
                    '    type B;\n'
                    '    type C;\n'
                    '    type D;\n'
                    '};'
                ),
                'generated_by': 'DDLStatement',
            },
        ])

    async def test_edgeql_migration_rewrite_02(self):
        await self.con.execute('''
            CONFIGURE SESSION SET store_migration_sdl :=
                cfg::StoreMigrationSDL.AlwaysStore;
        ''')

        # Simulate a potential migration squashing flow from the CLI,
        # where we generate a script using start migration and then apply it
        # later.
        await self.con.execute(r"""
            create type Foo;
            create type Tgt;
            alter type Foo { create link tgt -> Tgt; };
        """)

        await self.con.execute(r"""
            start migration rewrite;
        """)
        await self.con.execute(r"""
            start migration to committed schema;
        """)
        await self.con.execute(r"""
            populate migration;
        """)
        res = json.loads(await self.con.query_json(r"""
            describe current migration as json;
        """))

        await self.con.execute(r"""
            commit migration;
        """)
        await self.con.execute(r"""
            abort migration rewrite;
        """)

        self.assertTrue(res[0]['complete'])
        commands = '\n'.join(res[0]['confirmed'])
        script = textwrap.dedent('''\
            start migration rewrite;
            create migration {
            %s
            };
            commit migration rewrite;
        ''') % textwrap.indent(commands, ' ' * 4)

        await self.con.execute(script)

        await self.assert_migration_history([
            {
                'script': commands,
                'sdl': (
                    'module default {\n'
                    '    type Foo {\n'
                    '        link tgt: default::Tgt;\n'
                    '    };\n'
                    '    type Tgt;\n'
                    '};'
                ),
            }
        ])

    async def test_edgeql_migration_rewrite_03(self):
        await self.con.execute('''
            CONFIGURE SESSION SET store_migration_sdl :=
                cfg::StoreMigrationSDL.AlwaysStore;
        ''')

        # Test rolling back to a savepoint after a commit failure
        await self.con.execute(r"""
            create type A;
            create type B;
        """)

        await self.con.execute(r"""
            start migration rewrite;
        """)
        await self.con.execute(r"""
            declare savepoint s0;
        """)
        await self.con.execute(r"""
            create type B;
        """)
        await self.con.execute(r"""
            declare savepoint s1;
        """)
        with self.assertRaisesRegex(
                edgedb.QueryError,
                r"does not match"):
            await self.con.execute(r"""
                commit migration rewrite;
            """)

        # Rollback and try again
        await self.con.execute(r"""
            rollback to savepoint s1;
        """)
        await self.con.execute(r"""
            create type A;
        """)
        await self.con.execute(r"""
            commit migration rewrite
        """)

        gby = (
            'SET generated_by := (schema::MigrationGeneratedBy.DDLStatement);'
        )
        await self.assert_migration_history([
            {
                'script': gby + '\n' + 'CREATE TYPE B;',
                'sdl': (
                    'module default {\n'
                    '    type B;\n'
                    '};'
                ),
            },
            {
                'script': gby + '\n' + 'CREATE TYPE A;',
                'sdl': (
                    'module default {\n'
                    '    type A;\n'
                    '    type B;\n'
                    '};'
                ),
            },
        ])

    async def test_edgeql_migration_rewrite_05(self):
        # Test ABORT MIGRATION REWRITE
        await self.con.execute(r"""
            create type A;
            create type B;
        """)

        await self.con.execute(r"""
            start migration rewrite;
        """)
        with self.assertRaisesRegex(
                edgedb.QueryError,
                r"does not match"):
            await self.con.execute(r"""
                commit migration rewrite;
            """)
        await self.con.execute(r"""
            abort migration rewrite;
        """)

    async def test_edgeql_migration_rewrite_06(self):
        await self.con.execute('''
            CONFIGURE SESSION SET store_migration_sdl :=
                cfg::StoreMigrationSDL.AlwaysStore;
        ''')

        # Test doing the interactive migration flow
        await self.con.execute(r"""
            create type A;
            create type B;
        """)

        await self.con.execute(r"""
            start migration rewrite;
        """)

        await self.start_migration(r"""
            type A;
            type B;
        """, module='default')
        await self.fast_forward_describe_migration()

        await self.con.execute(r"""
            commit migration rewrite;
        """)

        await self.assert_migration_history([
            {
                'script': 'CREATE TYPE default::A;\nCREATE TYPE default::B;',
                'sdl': (
                    'module default {\n'
                    '    type A;\n'
                    '    type B;\n'
                    '};'
                ),
            },
        ])

    async def test_edgeql_migration_preexisting_01(self):
        await self.con.execute("create type Foo;")
        with self.assertRaisesRegex(
            edgedb.InvalidReferenceError,
            r"type 'default::Foo' does not exist"
        ):
            await self.start_migration("""
                type Bar {
                    baz: Foo
                }
            """, module='default')


class TestEdgeQLMigrationRewriteNonisolated(TestEdgeQLMigrationRewrite):
    # N.B: This test suite duplicates all the tests in the above
    # TestEdgeQLMigrationRewrite, but not in transactions.
    TRANSACTION_ISOLATION = False

    PER_TEST_TEARDOWN = [
        'rollback;',  # just in case, avoid extra errors
        '''
            start migration to { module default {}; };
            populate migration;
            commit migration;

            start migration rewrite;
            commit migration rewrite;
        ''',
    ]

    async def test_edgeql_migration_rewrite_raw_01(self):
        with self.assertRaisesRegex(
                edgedb.QueryError,
                r"Cannot leave an incomplete migration rewrite in scripts"):
            await self.con.execute(r"""
                START MIGRATION REWRITE;
                START MIGRATION TO {
                    module default {
                        type A;
                    }
                };
                POPULATE MIGRATION;
                COMMIT MIGRATION;
            """)

    @unittest.skipIf(
        True,
        """
        This test is still pretty slow
        """
    )
    async def test_edgeql_migration_rewrite_raw_02(self):
        for _ in range(1200):
            await self.con.execute(r"""
                create applied migration { }
            """)
